123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- /*
- * Dropbear - a SSH2 server
- *
- * Copyright (c) Matt Johnston
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE. */
- #include "includes.h"
- #include "session.h"
- #include "dbutil.h"
- #include "packet.h"
- #include "algo.h"
- #include "buffer.h"
- #include "dss.h"
- #include "ssh.h"
- #include "dbrandom.h"
- #include "kex.h"
- #include "channel.h"
- #include "runopts.h"
- #include "netio.h"
- static void checktimeouts(void);
- static long select_timeout(void);
- static int ident_readln(int fd, char* buf, int count);
- static void read_session_identification(void);
- struct sshsession ses; /* GLOBAL */
- /* need to know if the session struct has been initialised, this way isn't the
- * cleanest, but works OK */
- int sessinitdone = 0; /* GLOBAL */
- /* this is set when we get SIGINT or SIGTERM, the handler is in main.c */
- int exitflag = 0; /* GLOBAL */
- /* called only at the start of a session, set up initial state */
- void common_session_init(int sock_in, int sock_out) {
- time_t now;
- #ifdef DEBUG_TRACE
- debug_start_net();
- #endif
- TRACE(("enter session_init"))
- ses.sock_in = sock_in;
- ses.sock_out = sock_out;
- ses.maxfd = MAX(sock_in, sock_out);
- if (sock_in >= 0) {
- setnonblocking(sock_in);
- }
- if (sock_out >= 0) {
- setnonblocking(sock_out);
- }
- ses.socket_prio = DROPBEAR_PRIO_DEFAULT;
- /* Sets it to lowdelay */
- update_channel_prio();
- now = monotonic_now();
- ses.connect_time = now;
- ses.last_packet_time_keepalive_recv = now;
- ses.last_packet_time_idle = now;
- ses.last_packet_time_any_sent = 0;
- ses.last_packet_time_keepalive_sent = 0;
-
- if (pipe(ses.signal_pipe) < 0) {
- dropbear_exit("Signal pipe failed");
- }
- setnonblocking(ses.signal_pipe[0]);
- setnonblocking(ses.signal_pipe[1]);
- ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[0]);
- ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[1]);
-
- ses.writepayload = buf_new(TRANS_MAX_PAYLOAD_LEN);
- ses.transseq = 0;
- ses.readbuf = NULL;
- ses.payload = NULL;
- ses.recvseq = 0;
- initqueue(&ses.writequeue);
- ses.requirenext = SSH_MSG_KEXINIT;
- ses.dataallowed = 1; /* we can send data until we actually
- send the SSH_MSG_KEXINIT */
- ses.ignorenext = 0;
- ses.lastpacket = 0;
- ses.reply_queue_head = NULL;
- ses.reply_queue_tail = NULL;
- /* set all the algos to none */
- ses.keys = (struct key_context*)m_malloc(sizeof(struct key_context));
- ses.newkeys = NULL;
- ses.keys->recv.algo_crypt = &dropbear_nocipher;
- ses.keys->trans.algo_crypt = &dropbear_nocipher;
- ses.keys->recv.crypt_mode = &dropbear_mode_none;
- ses.keys->trans.crypt_mode = &dropbear_mode_none;
-
- ses.keys->recv.algo_mac = &dropbear_nohash;
- ses.keys->trans.algo_mac = &dropbear_nohash;
- ses.keys->algo_kex = NULL;
- ses.keys->algo_hostkey = -1;
- ses.keys->recv.algo_comp = DROPBEAR_COMP_NONE;
- ses.keys->trans.algo_comp = DROPBEAR_COMP_NONE;
- #ifndef DISABLE_ZLIB
- ses.keys->recv.zstream = NULL;
- ses.keys->trans.zstream = NULL;
- #endif
- /* key exchange buffers */
- ses.session_id = NULL;
- ses.kexhashbuf = NULL;
- ses.transkexinit = NULL;
- ses.dh_K = NULL;
- ses.remoteident = NULL;
- ses.chantypes = NULL;
- ses.allowprivport = 0;
- TRACE(("leave session_init"))
- }
- void session_loop(void(*loophandler)()) {
- fd_set readfd, writefd;
- struct timeval timeout;
- int val;
- /* main loop, select()s for all sockets in use */
- for(;;) {
- const int writequeue_has_space = (ses.writequeue_len <= 2*TRANS_MAX_PAYLOAD_LEN);
- timeout.tv_sec = select_timeout();
- timeout.tv_usec = 0;
- FD_ZERO(&writefd);
- FD_ZERO(&readfd);
- dropbear_assert(ses.payload == NULL);
- /* We get woken up when signal handlers write to this pipe.
- SIGCHLD in svr-chansession is the only one currently. */
- FD_SET(ses.signal_pipe[0], &readfd);
- ses.channel_signal_pending = 0;
- /* set up for channels which can be read/written */
- setchannelfds(&readfd, &writefd, writequeue_has_space);
- /* Pending connections to test */
- set_connect_fds(&writefd);
- /* We delay reading from the input socket during initial setup until
- after we have written out our initial KEXINIT packet (empty writequeue).
- This means our initial packet can be in-flight while we're doing a blocking
- read for the remote ident.
- We also avoid reading from the socket if the writequeue is full, that avoids
- replies backing up */
- if (ses.sock_in != -1
- && (ses.remoteident || isempty(&ses.writequeue))
- && writequeue_has_space) {
- FD_SET(ses.sock_in, &readfd);
- }
- /* Ordering is important, this test must occur after any other function
- might have queued packets (such as connection handlers) */
- if (ses.sock_out != -1 && !isempty(&ses.writequeue)) {
- FD_SET(ses.sock_out, &writefd);
- }
- val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout);
- if (exitflag) {
- dropbear_exit("Terminated by signal");
- }
-
- if (val < 0 && errno != EINTR) {
- dropbear_exit("Error in select");
- }
- if (val <= 0) {
- /* If we were interrupted or the select timed out, we still
- * want to iterate over channels etc for reading, to handle
- * server processes exiting etc.
- * We don't want to read/write FDs. */
- FD_ZERO(&writefd);
- FD_ZERO(&readfd);
- }
-
- /* We'll just empty out the pipe if required. We don't do
- any thing with the data, since the pipe's purpose is purely to
- wake up the select() above. */
- if (FD_ISSET(ses.signal_pipe[0], &readfd)) {
- char x;
- TRACE(("signal pipe set"))
- while (read(ses.signal_pipe[0], &x, 1) > 0) {}
- ses.channel_signal_pending = 1;
- }
- /* check for auth timeout, rekeying required etc */
- checktimeouts();
- /* process session socket's incoming data */
- if (ses.sock_in != -1) {
- if (FD_ISSET(ses.sock_in, &readfd)) {
- if (!ses.remoteident) {
- /* blocking read of the version string */
- read_session_identification();
- } else {
- read_packet();
- }
- }
-
- /* Process the decrypted packet. After this, the read buffer
- * will be ready for a new packet */
- if (ses.payload != NULL) {
- process_packet();
- }
- }
- /* if required, flush out any queued reply packets that
- were being held up during a KEX */
- maybe_flush_reply_queue();
- handle_connect_fds(&writefd);
- /* process pipes etc for the channels, ses.dataallowed == 0
- * during rekeying ) */
- channelio(&readfd, &writefd);
- /* process session socket's outgoing data */
- if (ses.sock_out != -1) {
- if (!isempty(&ses.writequeue)) {
- write_packet();
- }
- }
- if (loophandler) {
- loophandler();
- }
- } /* for(;;) */
-
- /* Not reached */
- }
- static void cleanup_buf(buffer **buf) {
- if (!*buf) {
- return;
- }
- buf_burn(*buf);
- buf_free(*buf);
- *buf = NULL;
- }
- /* clean up a session on exit */
- void session_cleanup() {
-
- TRACE(("enter session_cleanup"))
-
- /* we can't cleanup if we don't know the session state */
- if (!sessinitdone) {
- TRACE(("leave session_cleanup: !sessinitdone"))
- return;
- }
- /* BEWARE of changing order of functions here. */
- /* Must be before extra_session_cleanup() */
- chancleanup();
- if (ses.extra_session_cleanup) {
- ses.extra_session_cleanup();
- }
- /* After these are freed most functions will fail */
- #ifdef DROPBEAR_CLEANUP
- /* listeners call cleanup functions, this should occur before
- other session state is freed. */
- remove_all_listeners();
- remove_connect_pending();
- while (!isempty(&ses.writequeue)) {
- buf_free(dequeue(&ses.writequeue));
- }
- m_free(ses.remoteident);
- m_free(ses.authstate.pw_dir);
- m_free(ses.authstate.pw_name);
- m_free(ses.authstate.pw_shell);
- m_free(ses.authstate.pw_passwd);
- m_free(ses.authstate.username);
- #endif
- cleanup_buf(&ses.session_id);
- cleanup_buf(&ses.hash);
- cleanup_buf(&ses.payload);
- cleanup_buf(&ses.readbuf);
- cleanup_buf(&ses.writepayload);
- cleanup_buf(&ses.kexhashbuf);
- cleanup_buf(&ses.transkexinit);
- if (ses.dh_K) {
- mp_clear(ses.dh_K);
- }
- m_free(ses.dh_K);
- m_burn(ses.keys, sizeof(struct key_context));
- m_free(ses.keys);
- TRACE(("leave session_cleanup"))
- }
- void send_session_identification() {
- buffer *writebuf = buf_new(strlen(LOCAL_IDENT "\r\n") + 1);
- buf_putbytes(writebuf, (const unsigned char *) LOCAL_IDENT "\r\n", strlen(LOCAL_IDENT "\r\n"));
- writebuf_enqueue(writebuf, 0);
- }
- static void read_session_identification() {
- /* max length of 255 chars */
- char linebuf[256];
- int len = 0;
- char done = 0;
- int i;
- /* If they send more than 50 lines, something is wrong */
- for (i = 0; i < 50; i++) {
- len = ident_readln(ses.sock_in, linebuf, sizeof(linebuf));
- if (len < 0 && errno != EINTR) {
- /* It failed */
- break;
- }
- if (len >= 4 && memcmp(linebuf, "SSH-", 4) == 0) {
- /* start of line matches */
- done = 1;
- break;
- }
- }
- if (!done) {
- TRACE(("error reading remote ident: %s\n", strerror(errno)))
- ses.remoteclosed();
- } else {
- /* linebuf is already null terminated */
- ses.remoteident = m_malloc(len);
- memcpy(ses.remoteident, linebuf, len);
- }
- /* Shall assume that 2.x will be backwards compatible. */
- if (strncmp(ses.remoteident, "SSH-2.", 6) != 0
- && strncmp(ses.remoteident, "SSH-1.99-", 9) != 0) {
- dropbear_exit("Incompatible remote version '%s'", ses.remoteident);
- }
- TRACE(("remoteident: %s", ses.remoteident))
- }
- /* returns the length including null-terminating zero on success,
- * or -1 on failure */
- static int ident_readln(int fd, char* buf, int count) {
-
- char in;
- int pos = 0;
- int num = 0;
- fd_set fds;
- struct timeval timeout;
- TRACE(("enter ident_readln"))
- if (count < 1) {
- return -1;
- }
- FD_ZERO(&fds);
- /* select since it's a non-blocking fd */
-
- /* leave space to null-terminate */
- while (pos < count-1) {
- FD_SET(fd, &fds);
- timeout.tv_sec = 1;
- timeout.tv_usec = 0;
- if (select(fd+1, &fds, NULL, NULL, &timeout) < 0) {
- if (errno == EINTR) {
- continue;
- }
- TRACE(("leave ident_readln: select error"))
- return -1;
- }
- checktimeouts();
-
- /* Have to go one byte at a time, since we don't want to read past
- * the end, and have to somehow shove bytes back into the normal
- * packet reader */
- if (FD_ISSET(fd, &fds)) {
- num = read(fd, &in, 1);
- /* a "\n" is a newline, "\r" we want to read in and keep going
- * so that it won't be read as part of the next line */
- if (num < 0) {
- /* error */
- if (errno == EINTR) {
- continue; /* not a real error */
- }
- TRACE(("leave ident_readln: read error"))
- return -1;
- }
- if (num == 0) {
- /* EOF */
- TRACE(("leave ident_readln: EOF"))
- return -1;
- }
- if (in == '\n') {
- /* end of ident string */
- break;
- }
- /* we don't want to include '\r's */
- if (in != '\r') {
- buf[pos] = in;
- pos++;
- }
- }
- }
- buf[pos] = '\0';
- TRACE(("leave ident_readln: return %d", pos+1))
- return pos+1;
- }
- void ignore_recv_response() {
- /* Do nothing */
- TRACE(("Ignored msg_request_response"))
- }
- static void send_msg_keepalive() {
- time_t old_time_idle = ses.last_packet_time_idle;
- struct Channel *chan = get_any_ready_channel();
- CHECKCLEARTOWRITE();
- if (chan) {
- /* Channel requests are preferable, more implementations
- handle them than SSH_MSG_GLOBAL_REQUEST */
- TRACE(("keepalive channel request %d", chan->index))
- start_send_channel_request(chan, DROPBEAR_KEEPALIVE_STRING);
- } else {
- TRACE(("keepalive global request"))
- /* Some peers will reply with SSH_MSG_REQUEST_FAILURE,
- some will reply with SSH_MSG_UNIMPLEMENTED, some will exit. */
- buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST);
- buf_putstring(ses.writepayload, DROPBEAR_KEEPALIVE_STRING,
- strlen(DROPBEAR_KEEPALIVE_STRING));
- }
- buf_putbyte(ses.writepayload, 1); /* want_reply */
- encrypt_packet();
- ses.last_packet_time_keepalive_sent = monotonic_now();
- /* keepalives shouldn't update idle timeout, reset it back */
- ses.last_packet_time_idle = old_time_idle;
- }
- /* Check all timeouts which are required. Currently these are the time for
- * user authentication, and the automatic rekeying. */
- static void checktimeouts() {
- time_t now;
- now = monotonic_now();
-
- if (IS_DROPBEAR_SERVER && ses.connect_time != 0
- && now - ses.connect_time >= AUTH_TIMEOUT) {
- dropbear_close("Timeout before auth");
- }
- /* we can't rekey if we haven't done remote ident exchange yet */
- if (ses.remoteident == NULL) {
- return;
- }
- if (!ses.kexstate.sentkexinit
- && (now - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT
- || ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)) {
- TRACE(("rekeying after timeout or max data reached"))
- send_msg_kexinit();
- }
-
- if (opts.keepalive_secs > 0 && ses.authstate.authdone) {
- /* Avoid sending keepalives prior to auth - those are
- not valid pre-auth packet types */
- /* Send keepalives if we've been idle */
- if (now - ses.last_packet_time_any_sent >= opts.keepalive_secs) {
- send_msg_keepalive();
- }
- /* Also send an explicit keepalive message to trigger a response
- if the remote end hasn't sent us anything */
- if (now - ses.last_packet_time_keepalive_recv >= opts.keepalive_secs
- && now - ses.last_packet_time_keepalive_sent >= opts.keepalive_secs) {
- send_msg_keepalive();
- }
- if (now - ses.last_packet_time_keepalive_recv
- >= opts.keepalive_secs * DEFAULT_KEEPALIVE_LIMIT) {
- dropbear_exit("Keepalive timeout");
- }
- }
- if (opts.idle_timeout_secs > 0
- && now - ses.last_packet_time_idle >= opts.idle_timeout_secs) {
- dropbear_close("Idle timeout");
- }
- }
- static void update_timeout(long limit, long now, long last_event, long * timeout) {
- TRACE2(("update_timeout limit %ld, now %ld, last %ld, timeout %ld",
- limit, now, last_event, *timeout))
- if (last_event > 0 && limit > 0) {
- *timeout = MIN(*timeout, last_event+limit-now);
- TRACE2(("new timeout %ld", *timeout))
- }
- }
- static long select_timeout() {
- /* determine the minimum timeout that might be required, so
- as to avoid waking when unneccessary */
- long timeout = KEX_REKEY_TIMEOUT;
- long now = monotonic_now();
- if (!ses.kexstate.sentkexinit) {
- update_timeout(KEX_REKEY_TIMEOUT, now, ses.kexstate.lastkextime, &timeout);
- }
- if (ses.authstate.authdone != 1 && IS_DROPBEAR_SERVER) {
- /* AUTH_TIMEOUT is only relevant before authdone */
- update_timeout(AUTH_TIMEOUT, now, ses.connect_time, &timeout);
- }
- if (ses.authstate.authdone) {
- update_timeout(opts.keepalive_secs, now,
- MAX(ses.last_packet_time_keepalive_recv, ses.last_packet_time_keepalive_sent),
- &timeout);
- }
- update_timeout(opts.idle_timeout_secs, now, ses.last_packet_time_idle,
- &timeout);
- /* clamp negative timeouts to zero - event has already triggered */
- return MAX(timeout, 0);
- }
- const char* get_user_shell() {
- /* an empty shell should be interpreted as "/bin/sh" */
- if (ses.authstate.pw_shell[0] == '\0') {
- return "/bin/sh";
- } else {
- return ses.authstate.pw_shell;
- }
- }
- void fill_passwd(const char* username) {
- struct passwd *pw = NULL;
- if (ses.authstate.pw_name)
- m_free(ses.authstate.pw_name);
- if (ses.authstate.pw_dir)
- m_free(ses.authstate.pw_dir);
- if (ses.authstate.pw_shell)
- m_free(ses.authstate.pw_shell);
- if (ses.authstate.pw_passwd)
- m_free(ses.authstate.pw_passwd);
- pw = getpwnam(username);
- if (!pw) {
- return;
- }
- ses.authstate.pw_uid = pw->pw_uid;
- ses.authstate.pw_gid = pw->pw_gid;
- ses.authstate.pw_name = m_strdup(pw->pw_name);
- ses.authstate.pw_dir = m_strdup(pw->pw_dir);
- ses.authstate.pw_shell = m_strdup(pw->pw_shell);
- {
- char *passwd_crypt = pw->pw_passwd;
- #ifdef HAVE_SHADOW_H
- /* get the shadow password if possible */
- struct spwd *spasswd = getspnam(ses.authstate.pw_name);
- if (spasswd && spasswd->sp_pwdp) {
- passwd_crypt = spasswd->sp_pwdp;
- }
- #endif
- if (!passwd_crypt) {
- /* android supposedly returns NULL */
- passwd_crypt = "!!";
- }
- ses.authstate.pw_passwd = m_strdup(passwd_crypt);
- }
- }
- /* Called when channels are modified */
- void update_channel_prio() {
- enum dropbear_prio new_prio;
- int any = 0;
- unsigned int i;
- TRACE(("update_channel_prio"))
- if (ses.sock_out < 0) {
- TRACE(("leave update_channel_prio: no socket"))
- return;
- }
- new_prio = DROPBEAR_PRIO_BULK;
- for (i = 0; i < ses.chansize; i++) {
- struct Channel *channel = ses.channels[i];
- if (!channel || channel->prio == DROPBEAR_CHANNEL_PRIO_EARLY) {
- if (channel && channel->prio == DROPBEAR_CHANNEL_PRIO_EARLY) {
- TRACE(("update_channel_prio: early %d", channel->index))
- }
- continue;
- }
- any = 1;
- if (channel->prio == DROPBEAR_CHANNEL_PRIO_INTERACTIVE)
- {
- TRACE(("update_channel_prio: lowdelay %d", channel->index))
- new_prio = DROPBEAR_PRIO_LOWDELAY;
- break;
- } else if (channel->prio == DROPBEAR_CHANNEL_PRIO_UNKNOWABLE
- && new_prio == DROPBEAR_PRIO_BULK)
- {
- TRACE(("update_channel_prio: unknowable %d", channel->index))
- new_prio = DROPBEAR_PRIO_DEFAULT;
- }
- }
- if (any == 0) {
- /* lowdelay during setup */
- TRACE(("update_channel_prio: not any"))
- new_prio = DROPBEAR_PRIO_LOWDELAY;
- }
- if (new_prio != ses.socket_prio) {
- TRACE(("Dropbear priority transitioning %d -> %d", ses.socket_prio, new_prio))
- set_sock_priority(ses.sock_out, new_prio);
- ses.socket_prio = new_prio;
- }
- }
|