mirror of
https://github.com/clearml/dropbear
synced 2025-01-31 19:07:28 +00:00
70438b7715
and fb45ddf51e20f0ff007eb8abc737de3c024f45cc --HG-- extra : convert_revision : 2b620b0819e95e4181aa50b645a94e3a7f4d4840
370 lines
8.6 KiB
C
370 lines
8.6 KiB
C
/*
|
|
* 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");
|
|
}
|
|
|
|
/* 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();
|
|
}
|
|
}
|
|
|