blob: f2207535f0f7903ac519bf8205f5b986c27a0ae5 [file] [log] [blame]
/*
* 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 */
chaninitialise(); /* initialise the channel state */
ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN);
ses.transseq = 0;
ses.readbuf = NULL;
ses.decryptreadbuf = NULL;
ses.payload = NULL;
ses.recvseq = 0;
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);
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");
}
/* We allow up to 9 lines before the actual version string, to
* account for wrappers/cruft etc. According to the spec only the client
* needs to handle this, but no harm in letting the server handle it too */
for (i = 0; i < 10; 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);
}
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();
}
}