server session FEATURE accepting NETCONF sessions on more SSH channels

Also some other enhancements and refactoring.
diff --git a/src/session.c b/src/session.c
index 9554add..f53e105 100644
--- a/src/session.c
+++ b/src/session.c
@@ -49,6 +49,67 @@
 
 extern struct nc_server_opts server_opts;
 
+/*
+ * @return 1 - success
+ *         0 - timeout
+ *        -1 - error
+ */
+int
+nc_timedlock(pthread_mutex_t *lock, int timeout, int *elapsed)
+{
+    int ret;
+    struct timespec ts_timeout, ts_old, ts_new;
+
+    if (timeout > 0) {
+        clock_gettime(CLOCK_REALTIME, &ts_timeout);
+
+        if (elapsed) {
+            ts_old = ts_timeout;
+        }
+
+        ts_timeout.tv_sec += timeout / 1000;
+        ts_timeout.tv_nsec += (timeout % 1000) * 1000000;
+
+        ret = pthread_mutex_timedlock(lock, &ts_timeout);
+
+        if (elapsed) {
+            clock_gettime(CLOCK_REALTIME, &ts_new);
+
+            *elapsed += (ts_new.tv_sec - ts_old.tv_sec) * 1000;
+            *elapsed += (ts_new.tv_nsec - ts_old.tv_nsec) / 1000000;
+        }
+    } else if (!timeout) {
+        ret = pthread_mutex_trylock(lock);
+    } else { /* timeout == -1 */
+        ret = pthread_mutex_lock(lock);
+    }
+
+    if (ret == ETIMEDOUT) {
+        /* timeout */
+        return 0;
+    } else if (ret) {
+        /* error */
+        ERR("Mutex lock failed (%s).", strerror(errno));
+        return -1;
+    }
+
+    /* ok */
+    return 1;
+}
+
+void
+nc_subtract_elapsed(int *timeout, struct timespec *old_ts)
+{
+    struct timespec new_ts;
+
+    clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
+
+    *timeout -= (new_ts.tv_sec - old_ts->tv_sec) * 1000;
+    *timeout -= (new_ts.tv_nsec - old_ts->tv_nsec) / 1000000;
+
+    *old_ts = new_ts;
+}
+
 API NC_STATUS
 nc_session_get_status(const struct nc_session *session)
 {
@@ -166,54 +227,6 @@
     return NC_MSG_RPC;
 }
 
-/*
- * @return 1 - success
- *         0 - timeout
- *        -1 - error
- */
-int
-nc_timedlock(pthread_mutex_t *lock, int timeout, int *elapsed)
-{
-    int ret;
-    struct timespec ts_timeout, ts_old, ts_new;
-
-    if (timeout > 0) {
-        clock_gettime(CLOCK_REALTIME, &ts_timeout);
-
-        if (elapsed) {
-            ts_old = ts_timeout;
-        }
-
-        ts_timeout.tv_sec += timeout / 1000;
-        ts_timeout.tv_nsec += (timeout % 1000) * 1000000;
-
-        ret = pthread_mutex_timedlock(lock, &ts_timeout);
-
-        if (elapsed) {
-            clock_gettime(CLOCK_REALTIME, &ts_new);
-
-            *elapsed += (ts_new.tv_sec - ts_old.tv_sec) * 1000;
-            *elapsed += (ts_new.tv_nsec - ts_old.tv_nsec) / 1000000;
-        }
-    } else if (!timeout) {
-        ret = pthread_mutex_trylock(lock);
-    } else { /* timeout == -1 */
-        ret = pthread_mutex_lock(lock);
-    }
-
-    if (ret == ETIMEDOUT) {
-        /* timeout */
-        return 0;
-    } else if (ret) {
-        /* error */
-        ERR("Mutex lock failed (%s).", strerror(errno));
-        return -1;
-    }
-
-    /* ok */
-    return 1;
-}
-
 API void
 nc_session_free(struct nc_session *session)
 {
@@ -329,14 +342,44 @@
          * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
          * it.
          */
-        if (!session->ti.libssh.next) {
+        multisession = 0;
+        if (session->ti.libssh.next) {
+            for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
+                if (siter->status != NC_STATUS_STARTING) {
+                    multisession = 1;
+                    break;
+                }
+            }
+        }
+
+        if (!multisession) {
+            /* it's not multisession yet, but we still need to free the starting sessions */
+            if (session->ti.libssh.next) {
+                do {
+                    siter = session->ti.libssh.next;
+                    session->ti.libssh.next = siter->ti.libssh.next;
+
+                    /* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
+                    if (session->side == NC_SERVER) {
+                        nc_ctx_lock(-1, NULL);
+                    }
+                    lydict_remove(session->ctx, session->username);
+                    lydict_remove(session->ctx, session->host);
+                    if (session->side == NC_SERVER) {
+                        nc_ctx_unlock();
+                    }
+                    if (!(session->flags & NC_SESSION_SHAREDCTX)) {
+                        ly_ctx_destroy(session->ctx);
+                    }
+
+                    free(siter);
+                } while (session->ti.libssh.next != session);
+            }
             if (connected) {
                 ssh_disconnect(session->ti.libssh.session);
             }
             ssh_free(session->ti.libssh.session);
         } else {
-            /* multiple NETCONF sessions on a single SSH session */
-            multisession = 1;
             /* remove the session from the list */
             for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
             if (session->ti.libssh.next == siter) {
@@ -346,6 +389,17 @@
                 /* there are still multiple sessions, keep the ring list */
                 siter->ti.libssh.next = session->ti.libssh.next;
             }
+            /* change nc_sshcb_msg() argument, we need a RUNNING session and this one will be freed */
+            if (session->flags & NC_SESSION_SSH_MSG_CB) {
+                for (siter = session->ti.libssh.next; siter->status != NC_STATUS_RUNNING; siter = siter->ti.libssh.next) {
+                    if (siter->ti.libssh.next == session) {
+                        ERRINT;
+                        break;
+                    }
+                }
+                ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, siter);
+                siter->flags |= NC_SESSION_SSH_MSG_CB;
+            }
         }
         break;
 #endif
diff --git a/src/session_p.h b/src/session_p.h
index ddf7d56..87429cc 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -224,9 +224,14 @@
     /* server side only data */
     time_t last_rpc;
 #ifdef ENABLE_SSH
-    /* additional flags */
+    /* SSH session authenticated */
 #   define NC_SESSION_SSH_AUTHENTICATED 0x02
+    /* netconf subsystem requested */
 #   define NC_SESSION_SSH_SUBSYS_NETCONF 0x04
+    /* new SSH message arrived */
+#   define NC_SESSION_SSH_NEW_MSG 0x08
+    /* this session is passed to nc_sshcb_msg() */
+#   define NC_SESSION_SSH_MSG_CB 0x10
 
     uint16_t ssh_auth_attempts;
 #endif
@@ -245,6 +250,8 @@
 
 int nc_timedlock(pthread_mutex_t *lock, int timeout, int *elapsed);
 
+void nc_subtract_elapsed(int *timeout, struct timespec *old_ts);
+
 /**
  * @brief Fill libyang context in \p session. Context models are based on the stored session
  *        capabilities. If the server does not support \<get-schema\>, the models are searched
@@ -317,6 +324,32 @@
  */
 int nc_accept_ssh_session(struct nc_session *session, int sock, int timeout);
 
+/**
+ * @brief Callback called when a new SSH message is received.
+ *
+ * @param[in] sshsession SSH session the message arrived on.
+ * @param[in] msg SSH message itself.
+ * @param[in] data NETCONF session running on \p sshsession.
+ * @return 0 if the message was handled, 1 if it is left up to libssh.
+ */
+int nc_sshcb_msg(ssh_session sshsession, ssh_message msg, void *data);
+
+/**
+ * @brief Inspect what exactly happened if a SSH session socket poll
+ * returned POLLIN.
+ *
+ * @param[in] session NETCONF session communicating on the socket.
+ * @param[in,out] timeout Timeout for locking ti_lock, gets updated.
+ * @return 0 - timeout,
+ *         1 if \p session channel has data,
+ *         2 if some other channel has data,
+ *         3 on \p session status change,
+ *         4 on new SSH message,
+ *         5 on new NETCONF SSH channel,
+ *        -1 on error.
+ */
+int nc_ssh_pollin(struct nc_session *session, int *timeout);
+
 #endif
 
 #ifdef ENABLE_TLS
diff --git a/src/session_server.c b/src/session_server.c
index 4a5ce44..89b52ad 100644
--- a/src/session_server.c
+++ b/src/session_server.c
@@ -587,12 +587,12 @@
 nc_ps_poll(struct nc_pollsession *ps, int timeout)
 {
     int ret;
-    uint16_t i;
+    uint16_t i, j;
     time_t cur_time;
     NC_MSG_TYPE msgtype;
     struct nc_session *session;
     struct nc_server_rpc *rpc;
-    struct timespec old_ts, new_ts;
+    struct timespec old_ts;
 
     if (!ps || !ps->session_count) {
         ERRARG;
@@ -648,25 +648,35 @@
             return 3;
         } else if (ps->pfds[i].revents & POLLIN) {
 #ifdef ENABLE_SSH
-            if (ps->sessions[i].session->ti_type == NC_TI_LIBSSH) {
-                /* things are not that simple with SSH, we need to check the channel */
-                ret = ssh_channel_poll_timeout(ps->sessions[i].session->ti.libssh.channel, 0, 0);
-                /* not this one */
-                if (!ret) {
+            if (ps->sessions[i]->ti_type == NC_TI_LIBSSH) {
+                /* things are not that simple with SSH... */
+                ret = nc_ssh_pollin(ps->sessions[i], &timeout);
+
+                /* clear POLLIN on sessions sharing this session's SSH session */
+                if ((ret == 1) || (ret >= 4)) {
+                    for (j = i + 1; j < ps->session_count; ++j) {
+                        if (ps->pfds[j].fd == ps->pfds[i].fd) {
+                            ps->pfds[j].revents = 0;
+                        }
+                    }
+                }
+
+                /* actual event happened */
+                if ((ret <= 0) || (ret >= 3)) {
+                    ps->pfds[i].revents = 0;
+                    return ret;
+
+                /* event occurred on some other channel */
+                } else if (ret == 2) {
+                    ps->pfds[i].revents = 0;
                     if (i == ps->session_count - 1) {
                         /* last session and it is not the right channel, ... */
                         if (timeout > 0) {
                             /* ... decrease timeout, wait it all out and try again, last time */
-                            clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
-
-                            timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
-                            timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
-                            if (timeout < 0) {
-                                ERRINT;
-                                return -1;
-                            }
-
-                            old_ts = new_ts;
+                            nc_subtract_elapsed(&timeout, &old_ts);
+                            usleep(timeout * 1000);
+                            timeout = 0;
+                            goto retry_poll;
                         } else if (!timeout) {
                             /* ... timeout is 0, so that is it */
                             return 0;
@@ -677,20 +687,7 @@
                         }
                     }
                     /* check other sessions */
-                    ps->sessions[i].revents = 0;
                     continue;
-                } else if (ret == SSH_ERROR) {
-                    ERR("Session %u: SSH channel error (%s).", ps->sessions[i].session->id,
-                        ssh_get_error(ps->sessions[i].session->ti.libssh.session));
-                    ps->sessions[i].session->status = NC_STATUS_INVALID;
-                    ps->sessions[i].session->term_reason = NC_SESSION_TERM_OTHER;
-                    return 3;
-                } else if (ret == SSH_EOF) {
-                    ERR("Session %u: communication channel unexpectedly closed (libssh).",
-                        ps->sessions[i].session->id);
-                    ps->sessions[i].session->status = NC_STATUS_INVALID;
-                    ps->sessions[i].session->term_reason = NC_SESSION_TERM_DROPPED;
-                    return 3;
                 }
             }
 #endif /* ENABLE_SSH */
@@ -710,15 +707,7 @@
     session = ps->sessions[i];
 
     if (timeout > 0) {
-        clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
-
-        /* subtract elapsed time */
-        timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
-        timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
-        if (timeout < 0) {
-            ERRINT;
-            return -1;
-        }
+        nc_subtract_elapsed(&timeout, &old_ts);
     }
 
     /* reading an RPC and sending a reply must be atomic (no other RPC should be read) */
diff --git a/src/session_server.h b/src/session_server.h
index a9f18c3..a1bfb2a 100644
--- a/src/session_server.h
+++ b/src/session_server.h
@@ -175,8 +175,8 @@
  * @brief Poll sessions and process any received RPCs.
  *
  * All the sessions must be running. If a session fails causing it to change its
- * status, it can be learnt from the return value. Only one event (new RPC, TODO
- * new SSH channel request) on one session is handled in one function call.
+ * status, it can be learnt from the return value. Only one event on one session
+ * is handled in one function call.
  *
  * @param[in] ps Pollsession structure to use.
  * @param[in] timeout Poll timeout in milliseconds. 0 for non-blocking call, -1 for
@@ -186,6 +186,11 @@
  *         2 if an RPC was processed and there are unhandled events on other sessions,
  *         3 if a session from \p ps changed its status (was invalidated),
  *         -1 on error.
+ *
+ *         Only with SSH support:
+ *         4 if an SSH message was processed,
+ *         5 if a new NETCONF SSH channel was created; call nc_ssh_ps_accept_channel()
+ *           to establish a new NETCONF session.
  */
 int nc_ps_poll(struct nc_pollsession *ps, int timeout);
 
@@ -234,7 +239,7 @@
  *
  * @param[in] timeout Timeout for receiving a new connection in milliseconds, 0 for
  * non-blocking call, -1 for infinite waiting.
- * @param[out] session New session on success.
+ * @param[out] session New session.
  * @return 1 on success, 0 on timeout, -1 or error.
  */
 int nc_accept(int timeout, struct nc_session **session);
@@ -244,6 +249,16 @@
 #ifdef ENABLE_SSH
 
 /**
+ * @brief Accept a new NETCONF session on an SSH session of a running NETCONF session
+ * that was polled in \p ps. Call this function only when nc_ps_poll() on \p ps returns 5.
+ *
+ * @param[in] ps Unmodified pollsession structure from the previous nc_ps_poll() call.
+ * @param[out] session New session.
+ * @return 1 on success, -1 on error.
+ */
+int nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session);
+
+/**
  * @brief Set SSH host keys the server will identify itself with. Each of RSA, DSA, and
  * ECDSA key can be set. If the particular type was already set, it is replaced.
  *
diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c
index 4c1d6fb..3824c52 100644
--- a/src/session_server_ssh.c
+++ b/src/session_server_ssh.c
@@ -418,53 +418,87 @@
 }
 
 static int
-nc_sshcb_channel_open(struct nc_session *session, ssh_channel channel)
+nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
 {
-    while (session->ti.libssh.next) {
-        if (session->status == NC_STATUS_STARTING) {
+    ssh_channel chan;
+
+    /* first channel request */
+    if (!session->ti.libssh.channel) {
+        if (session->status != NC_STATUS_STARTING) {
             ERRINT;
             return -1;
         }
-        session = session->ti.libssh.next;
-    }
+        chan = ssh_message_channel_request_open_reply_accept(msg);
+        if (!chan) {
+            ERR("Failed to create a new SSH channel.");
+            return -1;
+        }
+        session->ti.libssh.channel = chan;
 
-    if ((session->status != NC_STATUS_STARTING) || session->ti.libssh.channel) {
-        ERRINT;
-        return -1;
+    /* additional channel request */
+    } else {
+        chan = ssh_message_channel_request_open_reply_accept(msg);
+        if (!chan) {
+            ERR("Session %u: failed to create a new SSH channel.", session->id);
+            return -1;
+        }
+        /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
     }
 
-    session->ti.libssh.channel = channel;
-
     return 0;
 }
 
 static int
 nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
 {
-    while (session && (session->ti.libssh.channel != channel)) {
-        session = session->ti.libssh.next;
-    }
+    struct nc_session *new_session;
 
-    if (!session) {
-        ERRINT;
+    if (strcmp(subsystem, "netconf")) {
+        WRN("Received an unknown subsystem \"%s\" request.", subsystem);
         return -1;
     }
 
-    if (!strcmp(subsystem, "netconf")) {
-        if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
-            WRN("Client \"%s\" requested subsystem \"netconf\" for the second time.", session->username);
-        } else {
-            session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
+    if (session->ti.libssh.channel == channel) {
+        /* first channel requested */
+        if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
+            ERRINT;
+            return -1;
         }
+        if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
+            ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
+            return -1;
+        }
+
+        session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
     } else {
-        WRN("Client \"%s\" requested an unknown subsystem \"%s\".", session->username, subsystem);
-        return -1;
+        /* additional channel subsystem request, new session is ready as far as SSH is concerned */
+        new_session = calloc(1, sizeof *new_session);
+
+        /* insert the new session */
+        if (!session->ti.libssh.next) {
+            new_session->ti.libssh.next = session;
+        } else {
+            new_session->ti.libssh.next = session->ti.libssh.next;
+        }
+        session->ti.libssh.next = new_session;
+
+        new_session->status = NC_STATUS_STARTING;
+        new_session->side = NC_SERVER;
+        new_session->ti_type = NC_TI_LIBSSH;
+        new_session->ti_lock = session->ti_lock;
+        new_session->ti.libssh.channel = channel;
+        new_session->ti.libssh.session = session->ti.libssh.session;
+        new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
+        new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
+        new_session->port = session->port;
+        new_session->ctx = server_opts.ctx;
+        new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
     }
 
     return 0;
 }
 
-static int
+int
 nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
 {
     const char *str_type, *str_subtype = NULL, *username;
@@ -583,6 +617,7 @@
     }
 
     VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
+    session->flags |= NC_SESSION_SSH_NEW_MSG;
 
     /*
      * process known messages
@@ -633,19 +668,17 @@
         }
     } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
         if ((type == SSH_REQUEST_CHANNEL_OPEN) && (subtype == (int)SSH_CHANNEL_SESSION)) {
-            ssh_channel chan;
-            if ((chan = ssh_message_channel_request_open_reply_accept(msg)) == NULL) {
+            if (nc_sshcb_channel_open(session, msg)) {
                 ssh_message_reply_default(msg);
-                return 0;
             }
-            nc_sshcb_channel_open(session, chan);
             return 0;
+
         } else if ((type == SSH_REQUEST_CHANNEL) && (subtype == (int)SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
-            if (!nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
-                                            ssh_message_channel_request_subsystem(msg))) {
-                ssh_message_channel_request_reply_success(msg);
-            } else {
+            if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
+                    ssh_message_channel_request_subsystem(msg))) {
                 ssh_message_reply_default(msg);
+            } else {
+                ssh_message_channel_request_reply_success(msg);
             }
             return 0;
         }
@@ -742,6 +775,73 @@
     return 0;
 }
 
+/* ret 0 - timeout, 1 channel has data, 2 some other channel has data,
+ * 3 status change, 4 new SSH message, 5 new NETCONF SSH channel, -1 error */
+int
+nc_ssh_pollin(struct nc_session *session, int *timeout)
+{
+    int ret, elapsed = 0;
+    struct nc_session *new;
+
+    ret = nc_timedlock(session->ti_lock, *timeout, &elapsed);
+    if (*timeout > 0) {
+        *timeout -= elapsed;
+    }
+
+    if (ret != 1) {
+        return ret;
+    }
+
+    ret = ssh_execute_message_callbacks(session->ti.libssh.session);
+    pthread_mutex_unlock(session->ti_lock);
+
+    if (ret != SSH_OK) {
+        ERR("Session %u: failed to receive SSH messages (%s).", session->id,
+            ssh_get_error(session->ti.libssh.session));
+        session->status = NC_STATUS_INVALID;
+        session->term_reason = NC_SESSION_TERM_OTHER;
+        return 3;
+    }
+
+    /* new SSH message */
+    if (session->flags & NC_SESSION_SSH_NEW_MSG) {
+        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 */
+                    return 5;
+                }
+            }
+        }
+
+        /* just some SSH message */
+        return 4;
+    }
+
+    /* no new SSH message, maybe NETCONF data? */
+    ret = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
+    /* not this one */
+    if (!ret) {
+        return 2;
+    } else if (ret == SSH_ERROR) {
+        ERR("Session %u: SSH channel error (%s).", session->id,
+            ssh_get_error(session->ti.libssh.session));
+        session->status = NC_STATUS_INVALID;
+        session->term_reason = NC_SESSION_TERM_OTHER;
+        return 3;
+    } else if (ret == SSH_EOF) {
+        ERR("Session %u: communication channel unexpectedly closed (libssh).",
+            session->id);
+        session->status = NC_STATUS_INVALID;
+        session->term_reason = NC_SESSION_TERM_DROPPED;
+        return 3;
+    }
+
+    return 1;
+}
+
 int
 nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
 {
@@ -768,6 +868,7 @@
     ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
 
     ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
+    session->flags |= NC_SESSION_SSH_MSG_CB;
 
     /* LOCK */
     ret = nc_timedlock(&ssh_opts.sshbind_lock, timeout, &elapsed);
@@ -827,48 +928,59 @@
         return ret;
     }
 
+    session->flags &= ~NC_SESSION_SSH_NEW_MSG;
+
     return 1;
 }
 
-/* TODO remove */
-struct nc_session *
-nc_accept_ssh_channel(struct nc_session *session, int timeout)
+API int
+nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
 {
-    struct nc_session *new_session;
-    int ret;
+    uint16_t i;
+    struct nc_session *new_session = NULL;
 
-    new_session = calloc(1, sizeof *new_session);
-    new_session->ti.libssh.session = session->ti.libssh.session;
-    ret = nc_open_netconf_channel(new_session, timeout);
-    if (ret) {
-        goto fail;
+    if (!ps || !session) {
+        ERRARG;
+        return -1;
     }
 
-    /* new channel was requested and opened, fill in the whole session now */
-    for (; session->ti.libssh.next; session = session->ti.libssh.next);
-    session->ti.libssh.next = new_session;
+    for (i = 0; i < ps->session_count; ++i) {
+        if ((ps->sessions[i]->status == NC_STATUS_RUNNING) && (ps->sessions[i]->ti_type == NC_TI_LIBSSH)
+                && ps->sessions[i]->ti.libssh.next) {
+            /* an SSH session with more channels */
+            for (new_session = ps->sessions[i]->ti.libssh.next;
+                    new_session != ps->sessions[i];
+                    new_session = new_session->ti.libssh.next) {
+                if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
+                        && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
+                    /* we found our session */
+                    break;
+                }
+            }
+            if (new_session != ps->sessions[i]) {
+                break;
+            }
 
-    new_session->status = NC_STATUS_STARTING;
-    new_session->side = NC_SERVER;
-    new_session->ti_type = NC_TI_LIBSSH;
-    new_session->ti_lock = session->ti_lock;
-    new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SHAREDCTX;
-    new_session->ctx = server_opts.ctx;
+            new_session = NULL;
+        }
+    }
 
-    new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
-    new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
-    new_session->port = session->port;
+    if (!new_session) {
+        ERR("No session with a NETCONF SSH channel ready was found.");
+        return -1;
+    }
+
+    /* assign new SID atomically */
+    pthread_spin_lock(&server_opts.sid_lock);
+    new_session->id = server_opts.new_session_id++;
+    pthread_spin_unlock(&server_opts.sid_lock);
 
     /* NETCONF handshake */
     if (nc_handshake(new_session)) {
-        goto fail;
+        return -1;
     }
     new_session->status = NC_STATUS_RUNNING;
+    *session = new_session;
 
-    return new_session;
-
-fail:
-    nc_session_free(new_session);
-
-    return NULL;
+    return 0;
 }