mirror of
http://galexander.org/git/simplesshd.git
synced 2024-11-14 02:59:05 +00:00
475 lines
13 KiB
C
475 lines
13 KiB
C
/*
|
|
* Dropbear SSH
|
|
*
|
|
* Copyright (c) 2002,2003 Matt Johnston
|
|
* Copyright (c) 2004 by Mihnea Stoenescu
|
|
* 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 "kex.h"
|
|
#include "ssh.h"
|
|
#include "packet.h"
|
|
#include "tcpfwd.h"
|
|
#include "channel.h"
|
|
#include "dbrandom.h"
|
|
#include "service.h"
|
|
#include "runopts.h"
|
|
#include "chansession.h"
|
|
#include "agentfwd.h"
|
|
#include "crypto_desc.h"
|
|
#include "netio.h"
|
|
|
|
static void cli_remoteclosed(void) ATTRIB_NORETURN;
|
|
static void cli_sessionloop(void);
|
|
static void cli_session_init(pid_t proxy_cmd_pid);
|
|
static void cli_finished(void) ATTRIB_NORETURN;
|
|
static void recv_msg_service_accept(void);
|
|
static void cli_session_cleanup(void);
|
|
static void recv_msg_global_request_cli(void);
|
|
|
|
struct clientsession cli_ses; /* GLOBAL */
|
|
|
|
/* Sorted in decreasing frequency will be more efficient - data and window
|
|
* should be first */
|
|
static const packettype cli_packettypes[] = {
|
|
/* TYPE, FUNCTION */
|
|
{SSH_MSG_CHANNEL_DATA, recv_msg_channel_data},
|
|
{SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data},
|
|
{SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust},
|
|
{SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */
|
|
{SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */
|
|
{SSH_MSG_KEXINIT, recv_msg_kexinit},
|
|
{SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */
|
|
{SSH_MSG_NEWKEYS, recv_msg_newkeys},
|
|
{SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */
|
|
{SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request},
|
|
{SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open},
|
|
{SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof},
|
|
{SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close},
|
|
{SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation},
|
|
{SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure},
|
|
{SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */
|
|
{SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */
|
|
{SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_cli},
|
|
{SSH_MSG_CHANNEL_SUCCESS, ignore_recv_response},
|
|
{SSH_MSG_CHANNEL_FAILURE, ignore_recv_response},
|
|
#if DROPBEAR_CLI_REMOTETCPFWD
|
|
{SSH_MSG_REQUEST_SUCCESS, cli_recv_msg_request_success}, /* client */
|
|
{SSH_MSG_REQUEST_FAILURE, cli_recv_msg_request_failure}, /* client */
|
|
#else
|
|
/* For keepalive */
|
|
{SSH_MSG_REQUEST_SUCCESS, ignore_recv_response},
|
|
{SSH_MSG_REQUEST_FAILURE, ignore_recv_response},
|
|
#endif
|
|
{SSH_MSG_EXT_INFO, recv_msg_ext_info},
|
|
{0, NULL} /* End */
|
|
};
|
|
|
|
static const struct ChanType *cli_chantypes[] = {
|
|
#if DROPBEAR_CLI_REMOTETCPFWD
|
|
&cli_chan_tcpremote,
|
|
#endif
|
|
#if DROPBEAR_CLI_AGENTFWD
|
|
&cli_chan_agent,
|
|
#endif
|
|
NULL /* Null termination */
|
|
};
|
|
|
|
void cli_connected(int result, int sock, void* userdata, const char *errstring)
|
|
{
|
|
struct sshsession *myses = userdata;
|
|
if (result == DROPBEAR_FAILURE) {
|
|
dropbear_exit("Connect failed: %s", errstring);
|
|
}
|
|
myses->sock_in = myses->sock_out = sock;
|
|
update_channel_prio();
|
|
}
|
|
|
|
void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) {
|
|
|
|
common_session_init(sock_in, sock_out);
|
|
|
|
if (progress) {
|
|
connect_set_writequeue(progress, &ses.writequeue);
|
|
}
|
|
|
|
chaninitialise(cli_chantypes);
|
|
|
|
/* Set up cli_ses vars */
|
|
cli_session_init(proxy_cmd_pid);
|
|
|
|
/* Ready to go */
|
|
ses.init_done = 1;
|
|
|
|
/* Exchange identification */
|
|
send_session_identification();
|
|
|
|
kexfirstinitialise(); /* initialise the kex state */
|
|
|
|
send_msg_kexinit();
|
|
|
|
session_loop(cli_sessionloop);
|
|
|
|
/* Not reached */
|
|
|
|
}
|
|
|
|
#if DROPBEAR_KEX_FIRST_FOLLOWS
|
|
static void cli_send_kex_first_guess() {
|
|
send_msg_kexdh_init();
|
|
}
|
|
#endif
|
|
|
|
static void cli_session_init(pid_t proxy_cmd_pid) {
|
|
|
|
cli_ses.state = STATE_NOTHING;
|
|
cli_ses.kex_state = KEX_NOTHING;
|
|
|
|
cli_ses.tty_raw_mode = 0;
|
|
cli_ses.winchange = 0;
|
|
|
|
/* We store std{in,out,err}'s flags, so we can set them back on exit
|
|
* (otherwise busybox's ash isn't happy */
|
|
cli_ses.stdincopy = dup(STDIN_FILENO);
|
|
cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
cli_ses.stdoutcopy = dup(STDOUT_FILENO);
|
|
cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0);
|
|
cli_ses.stderrcopy = dup(STDERR_FILENO);
|
|
cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0);
|
|
|
|
cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a
|
|
specific exit status */
|
|
cli_ses.proxy_cmd_pid = proxy_cmd_pid;
|
|
TRACE(("proxy command PID='%d'", proxy_cmd_pid));
|
|
|
|
/* Auth */
|
|
cli_ses.lastprivkey = NULL;
|
|
cli_ses.lastauthtype = 0;
|
|
|
|
/* For printing "remote host closed" for the user */
|
|
ses.remoteclosed = cli_remoteclosed;
|
|
|
|
ses.extra_session_cleanup = cli_session_cleanup;
|
|
|
|
/* packet handlers */
|
|
ses.packettypes = cli_packettypes;
|
|
|
|
ses.isserver = 0;
|
|
|
|
#if DROPBEAR_KEX_FIRST_FOLLOWS
|
|
ses.send_kex_first_guess = cli_send_kex_first_guess;
|
|
#endif
|
|
|
|
}
|
|
|
|
static void send_msg_service_request(const char* servicename) {
|
|
|
|
TRACE(("enter send_msg_service_request: servicename='%s'", servicename))
|
|
|
|
CHECKCLEARTOWRITE();
|
|
|
|
buf_putbyte(ses.writepayload, SSH_MSG_SERVICE_REQUEST);
|
|
buf_putstring(ses.writepayload, servicename, strlen(servicename));
|
|
|
|
encrypt_packet();
|
|
TRACE(("leave send_msg_service_request"))
|
|
}
|
|
|
|
static void recv_msg_service_accept(void) {
|
|
/* do nothing, if it failed then the server MUST have disconnected */
|
|
}
|
|
|
|
/* This function drives the progress of the session - it initiates KEX,
|
|
* service, userauth and channel requests */
|
|
static void cli_sessionloop() {
|
|
|
|
TRACE2(("enter cli_sessionloop"))
|
|
|
|
if (ses.lastpacket == 0) {
|
|
TRACE2(("exit cli_sessionloop: no real packets yet"))
|
|
return;
|
|
}
|
|
|
|
if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) {
|
|
/* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
|
|
* negotiation would have failed. */
|
|
if (!ses.kexstate.our_first_follows_matches) {
|
|
send_msg_kexdh_init();
|
|
}
|
|
cli_ses.kex_state = KEXDH_INIT_SENT;
|
|
TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
|
|
return;
|
|
}
|
|
|
|
/* A KEX has finished, so we should go back to our KEX_NOTHING state */
|
|
if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.sentnewkeys) {
|
|
cli_ses.kex_state = KEX_NOTHING;
|
|
}
|
|
|
|
/* We shouldn't do anything else if a KEX is in progress */
|
|
if (cli_ses.kex_state != KEX_NOTHING) {
|
|
TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
|
|
return;
|
|
}
|
|
|
|
if (ses.kexstate.donefirstkex == 0) {
|
|
/* We might reach here if we have partial packet reads or have
|
|
* received SSG_MSG_IGNORE etc. Just skip it */
|
|
TRACE2(("donefirstkex false\n"))
|
|
return;
|
|
}
|
|
|
|
switch (cli_ses.state) {
|
|
|
|
case STATE_NOTHING:
|
|
/* We've got the transport layer sorted, we now need to request
|
|
* userauth */
|
|
send_msg_service_request(SSH_SERVICE_USERAUTH);
|
|
cli_auth_getmethods();
|
|
cli_ses.state = USERAUTH_REQ_SENT;
|
|
TRACE(("leave cli_sessionloop: sent userauth methods req"))
|
|
return;
|
|
|
|
case USERAUTH_REQ_SENT:
|
|
TRACE(("leave cli_sessionloop: waiting, req_sent"))
|
|
return;
|
|
|
|
case USERAUTH_FAIL_RCVD:
|
|
if (cli_auth_try() == DROPBEAR_FAILURE) {
|
|
dropbear_exit("No auth methods could be used.");
|
|
}
|
|
cli_ses.state = USERAUTH_REQ_SENT;
|
|
TRACE(("leave cli_sessionloop: cli_auth_try"))
|
|
return;
|
|
|
|
case USERAUTH_SUCCESS_RCVD:
|
|
#ifndef DISABLE_SYSLOG
|
|
if (opts.usingsyslog) {
|
|
dropbear_log(LOG_INFO, "Authentication succeeded.");
|
|
}
|
|
#endif
|
|
|
|
if (cli_opts.backgrounded) {
|
|
int devnull;
|
|
/* keeping stdin open steals input from the terminal and
|
|
is confusing, though stdout/stderr could be useful. */
|
|
devnull = open(DROPBEAR_PATH_DEVNULL, O_RDONLY);
|
|
if (devnull < 0) {
|
|
dropbear_exit("Opening /dev/null: %d %s",
|
|
errno, strerror(errno));
|
|
}
|
|
dup2(devnull, STDIN_FILENO);
|
|
if (daemon(0, 1) < 0) {
|
|
dropbear_exit("Backgrounding failed: %d %s",
|
|
errno, strerror(errno));
|
|
}
|
|
}
|
|
|
|
#if DROPBEAR_CLI_NETCAT
|
|
if (cli_opts.netcat_host) {
|
|
cli_send_netcat_request();
|
|
} else
|
|
#endif
|
|
if (!cli_opts.no_cmd) {
|
|
cli_send_chansess_request();
|
|
}
|
|
|
|
#if DROPBEAR_CLI_LOCALTCPFWD
|
|
setup_localtcp();
|
|
#endif
|
|
#if DROPBEAR_CLI_REMOTETCPFWD
|
|
setup_remotetcp();
|
|
#endif
|
|
|
|
TRACE(("leave cli_sessionloop: running"))
|
|
cli_ses.state = SESSION_RUNNING;
|
|
return;
|
|
|
|
case SESSION_RUNNING:
|
|
if (ses.chancount < 1 && !cli_opts.no_cmd) {
|
|
cli_finished();
|
|
}
|
|
|
|
if (cli_ses.winchange) {
|
|
cli_chansess_winchange();
|
|
}
|
|
return;
|
|
|
|
/* XXX more here needed */
|
|
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
TRACE2(("leave cli_sessionloop: fell out"))
|
|
|
|
}
|
|
|
|
void kill_proxy_command(void) {
|
|
/*
|
|
* Send SIGHUP to proxy command if used. We don't wait() in
|
|
* case it hangs and instead rely on init to reap the child
|
|
*/
|
|
if (cli_ses.proxy_cmd_pid > 1) {
|
|
TRACE(("killing proxy command with PID='%d'", cli_ses.proxy_cmd_pid));
|
|
kill(cli_ses.proxy_cmd_pid, SIGHUP);
|
|
}
|
|
}
|
|
|
|
static void cli_session_cleanup(void) {
|
|
|
|
if (!ses.init_done) {
|
|
return;
|
|
}
|
|
|
|
kill_proxy_command();
|
|
|
|
/* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
|
|
* we don't revert the flags */
|
|
/* Ignore return value since there's nothing we can do */
|
|
(void)fcntl(cli_ses.stdincopy, F_SETFL, cli_ses.stdinflags);
|
|
(void)fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags);
|
|
(void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags);
|
|
|
|
/* Don't leak */
|
|
m_close(cli_ses.stdincopy);
|
|
m_close(cli_ses.stdoutcopy);
|
|
m_close(cli_ses.stderrcopy);
|
|
|
|
cli_tty_cleanup();
|
|
if (cli_ses.server_sig_algs) {
|
|
buf_free(cli_ses.server_sig_algs);
|
|
}
|
|
}
|
|
|
|
static void cli_finished() {
|
|
TRACE(("cli_finished()"))
|
|
|
|
session_cleanup();
|
|
fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username,
|
|
cli_opts.remotehost, cli_opts.remoteport);
|
|
exit(cli_ses.retval);
|
|
}
|
|
|
|
|
|
/* called when the remote side closes the connection */
|
|
static void cli_remoteclosed() {
|
|
|
|
/* XXX TODO perhaps print a friendlier message if we get this but have
|
|
* already sent/received disconnect message(s) ??? */
|
|
m_close(ses.sock_in);
|
|
m_close(ses.sock_out);
|
|
ses.sock_in = -1;
|
|
ses.sock_out = -1;
|
|
dropbear_exit("Remote closed the connection");
|
|
}
|
|
|
|
/* Operates in-place turning dirty (untrusted potentially containing control
|
|
* characters) text into clean text.
|
|
* Note: this is safe only with ascii - other charsets could have problems. */
|
|
void cleantext(char* dirtytext) {
|
|
|
|
unsigned int i, j;
|
|
char c;
|
|
|
|
j = 0;
|
|
for (i = 0; dirtytext[i] != '\0'; i++) {
|
|
|
|
c = dirtytext[i];
|
|
/* We can ignore '\r's */
|
|
if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') {
|
|
dirtytext[j] = c;
|
|
j++;
|
|
}
|
|
}
|
|
/* Null terminate */
|
|
dirtytext[j] = '\0';
|
|
}
|
|
|
|
static void recv_msg_global_request_cli(void) {
|
|
TRACE(("recv_msg_global_request_cli"))
|
|
/* Send a proper rejection */
|
|
send_msg_request_failure();
|
|
}
|
|
|
|
void cli_dropbear_exit(int exitcode, const char* format, va_list param) {
|
|
char exitmsg[150];
|
|
char fullmsg[300];
|
|
|
|
/* Note that exit message must be rendered before session cleanup */
|
|
|
|
/* Render the formatted exit message */
|
|
vsnprintf(exitmsg, sizeof(exitmsg), format, param);
|
|
TRACE(("Exited, cleaning up: %s", exitmsg))
|
|
|
|
/* Add the prefix depending on session/auth state */
|
|
if (!ses.init_done) {
|
|
snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg);
|
|
} else {
|
|
snprintf(fullmsg, sizeof(fullmsg),
|
|
"Connection to %s@%s:%s exited: %s",
|
|
cli_opts.username, cli_opts.remotehost,
|
|
cli_opts.remoteport, exitmsg);
|
|
}
|
|
|
|
/* Do the cleanup first, since then the terminal will be reset */
|
|
session_cleanup();
|
|
|
|
#if DROPBEAR_FUZZ
|
|
if (fuzz.do_jmp) {
|
|
longjmp(fuzz.jmp, 1);
|
|
}
|
|
#endif
|
|
|
|
/* Avoid printing onwards from terminal cruft */
|
|
fprintf(stderr, "\n");
|
|
|
|
dropbear_log(LOG_INFO, "%s", fullmsg);
|
|
|
|
exit(exitcode);
|
|
}
|
|
|
|
void cli_dropbear_log(int priority, const char* format, va_list param) {
|
|
|
|
char printbuf[1024];
|
|
const char *name;
|
|
|
|
name = cli_opts.progname;
|
|
if (!name) {
|
|
name = "dbclient";
|
|
}
|
|
|
|
vsnprintf(printbuf, sizeof(printbuf), format, param);
|
|
|
|
#ifndef DISABLE_SYSLOG
|
|
if (opts.usingsyslog) {
|
|
syslog(priority, "%s", printbuf);
|
|
}
|
|
#endif
|
|
|
|
fprintf(stderr, "%s: %s\n", name, printbuf);
|
|
fflush(stderr);
|
|
}
|
|
|