diff options
Diffstat (limited to 'common-session.c')
-rw-r--r-- | common-session.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/common-session.c b/common-session.c new file mode 100644 index 0000000..4c15391 --- /dev/null +++ b/common-session.c @@ -0,0 +1,373 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 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 "random.h" +#include "kex.h" +#include "channel.h" +#include "atomicio.h" + +static void checktimeouts(); +static int ident_readln(int fd, char* buf, int count); + +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, char* remotehost) { + + TRACE(("enter session_init")) + + ses.remotehost = remotehost; + + ses.sock = sock; + ses.maxfd = sock; + + ses.connecttimeout = 0; + + kexfirstinitialise(); /* initialise the kex state */ + + ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN); + ses.transseq = 0; + + ses.readbuf = NULL; + ses.decryptreadbuf = NULL; + ses.payload = NULL; + ses.recvseq = 0; + + initqueue(&ses.writequeue); + + ses.requirenext = SSH_MSG_KEXINIT; + ses.dataallowed = 0; /* don't send data yet, we'll wait until after kex */ + ses.ignorenext = 0; + ses.lastpacket = 0; + + /* 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_algo_mac = &dropbear_nohash; + ses.keys->trans_algo_mac = &dropbear_nohash; + + ses.keys->algo_kex = -1; + 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(;;) { + + timeout.tv_sec = SELECT_TIMEOUT; + timeout.tv_usec = 0; + FD_ZERO(&writefd); + FD_ZERO(&readfd); + dropbear_assert(ses.payload == NULL); + if (ses.sock != -1) { + FD_SET(ses.sock, &readfd); + if (!isempty(&ses.writequeue)) { + FD_SET(ses.sock, &writefd); + } + } + + /* set up for channels which require reading/writing */ + if (ses.dataallowed) { + setchannelfds(&readfd, &writefd); + } + val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout); + + if (exitflag) { + dropbear_exit("Terminated by signal"); + } + + if (val < 0) { + if (errno == EINTR) { + /* This must happen even if we've been interrupted, so that + * changed signal-handler vars can take effect etc */ + if (loophandler) { + loophandler(); + } + continue; + } else { + dropbear_exit("Error in select"); + } + } + + /* check for auth timeout, rekeying required etc */ + checktimeouts(); + + if (val == 0) { + /* timeout */ + TRACE(("select timeout")) + continue; + } + + /* process session socket's incoming/outgoing data */ + if (ses.sock != -1) { + if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) { + write_packet(); + } + + if (FD_ISSET(ses.sock, &readfd)) { + read_packet(); + } + + /* Process the decrypted packet. After this, the read buffer + * will be ready for a new packet */ + if (ses.payload != NULL) { + process_packet(); + } + } + + /* process pipes etc for the channels, ses.dataallowed == 0 + * during rekeying ) */ + if (ses.dataallowed) { + channelio(&readfd, &writefd); + } + + if (loophandler) { + loophandler(); + } + + } /* for(;;) */ + + /* Not reached */ +} + +/* clean up a session on exit */ +void common_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; + } + + m_free(ses.session_id); + m_burn(ses.keys, sizeof(struct key_context)); + m_free(ses.keys); + + chancleanup(); + + TRACE(("leave session_cleanup")) +} + + +void session_identification() { + + /* max length of 255 chars */ + char linebuf[256]; + int len = 0; + char done = 0; + int i; + + /* write our version string, this blocks */ + if (atomicio(write, ses.sock, LOCAL_IDENT "\r\n", + strlen(LOCAL_IDENT "\r\n")) == DROPBEAR_FAILURE) { + dropbear_exit("Error writing ident string"); + } + + /* If they send more than 50 lines, something is wrong */ + for (i = 0; i < 50; i++) { + len = ident_readln(ses.sock, 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(("err: %s for '%s'\n", strerror(errno), linebuf)) + dropbear_exit("Failed to get remote version"); + } 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; +} + +/* Check all timeouts which are required. Currently these are the time for + * user authentication, and the automatic rekeying. */ +static void checktimeouts() { + + struct timeval tv; + long secs; + + if (gettimeofday(&tv, 0) < 0) { + dropbear_exit("Error getting time"); + } + + secs = tv.tv_sec; + + if (ses.connecttimeout != 0 && secs > ses.connecttimeout) { + 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 + && (secs - 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(); + } +} + |