4 * Copyright (c) 2002,2003 Matt Johnston
5 * Copyright (c) 2004 by Mihnea Stoenescu
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 #include "chansession.h"
40 static void cli_remoteclosed();
41 static void cli_sessionloop();
42 static void cli_session_init();
43 static void cli_finished();
45 struct clientsession cli_ses
; /* GLOBAL */
47 /* Sorted in decreasing frequency will be more efficient - data and window
49 static const packettype cli_packettypes
[] = {
51 {SSH_MSG_CHANNEL_DATA
, recv_msg_channel_data
},
52 {SSH_MSG_CHANNEL_EXTENDED_DATA
, recv_msg_channel_extended_data
},
53 {SSH_MSG_CHANNEL_WINDOW_ADJUST
, recv_msg_channel_window_adjust
},
54 {SSH_MSG_USERAUTH_FAILURE
, recv_msg_userauth_failure
}, /* client */
55 {SSH_MSG_USERAUTH_SUCCESS
, recv_msg_userauth_success
}, /* client */
56 {SSH_MSG_KEXINIT
, recv_msg_kexinit
},
57 {SSH_MSG_KEXDH_REPLY
, recv_msg_kexdh_reply
}, /* client */
58 {SSH_MSG_NEWKEYS
, recv_msg_newkeys
},
59 {SSH_MSG_SERVICE_ACCEPT
, recv_msg_service_accept
}, /* client */
60 {SSH_MSG_CHANNEL_REQUEST
, recv_msg_channel_request
},
61 {SSH_MSG_CHANNEL_OPEN
, recv_msg_channel_open
},
62 {SSH_MSG_CHANNEL_EOF
, recv_msg_channel_eof
},
63 {SSH_MSG_CHANNEL_CLOSE
, recv_msg_channel_close
},
64 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION
, recv_msg_channel_open_confirmation
},
65 {SSH_MSG_CHANNEL_OPEN_FAILURE
, recv_msg_channel_open_failure
},
66 {SSH_MSG_USERAUTH_BANNER
, recv_msg_userauth_banner
}, /* client */
67 {SSH_MSG_USERAUTH_SPECIFIC_60
, recv_msg_userauth_specific_60
}, /* client */
68 #ifdef ENABLE_CLI_REMOTETCPFWD
69 {SSH_MSG_REQUEST_SUCCESS
, cli_recv_msg_request_success
}, /* client */
70 {SSH_MSG_REQUEST_FAILURE
, cli_recv_msg_request_failure
}, /* client */
75 static const struct ChanType
*cli_chantypes
[] = {
76 #ifdef ENABLE_CLI_REMOTETCPFWD
79 #ifdef ENABLE_CLI_AGENTFWD
82 NULL
/* Null termination */
85 void cli_session(int sock_in
, int sock_out
) {
91 common_session_init(sock_in
, sock_out
);
93 chaninitialise(cli_chantypes
);
95 /* Set up cli_ses vars */
101 /* Exchange identification */
102 session_identification();
106 session_loop(cli_sessionloop
);
112 static void cli_session_init() {
114 cli_ses
.state
= STATE_NOTHING
;
115 cli_ses
.kex_state
= KEX_NOTHING
;
117 cli_ses
.tty_raw_mode
= 0;
118 cli_ses
.winchange
= 0;
120 /* We store std{in,out,err}'s flags, so we can set them back on exit
121 * (otherwise busybox's ash isn't happy */
122 cli_ses
.stdincopy
= dup(STDIN_FILENO
);
123 cli_ses
.stdinflags
= fcntl(STDIN_FILENO
, F_GETFL
, 0);
124 cli_ses
.stdoutcopy
= dup(STDOUT_FILENO
);
125 cli_ses
.stdoutflags
= fcntl(STDOUT_FILENO
, F_GETFL
, 0);
126 cli_ses
.stderrcopy
= dup(STDERR_FILENO
);
127 cli_ses
.stderrflags
= fcntl(STDERR_FILENO
, F_GETFL
, 0);
129 cli_ses
.retval
= EXIT_SUCCESS
; /* Assume it's clean if we don't get a
130 specific exit status */
133 cli_ses
.lastprivkey
= NULL
;
134 cli_ses
.lastauthtype
= 0;
136 /* For printing "remote host closed" for the user */
137 ses
.remoteclosed
= cli_remoteclosed
;
138 ses
.buf_match_algo
= cli_buf_match_algo
;
140 /* packet handlers */
141 ses
.packettypes
= cli_packettypes
;
146 /* This function drives the progress of the session - it initiates KEX,
147 * service, userauth and channel requests */
148 static void cli_sessionloop() {
150 TRACE(("enter cli_sessionloop"))
152 if (ses
.lastpacket
== SSH_MSG_KEXINIT
&& cli_ses
.kex_state
== KEX_NOTHING
) {
153 cli_ses
.kex_state
= KEXINIT_RCVD
;
156 if (cli_ses
.kex_state
== KEXINIT_RCVD
) {
158 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
159 * negotiation would have failed. */
160 send_msg_kexdh_init();
161 cli_ses
.kex_state
= KEXDH_INIT_SENT
;
162 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
166 /* A KEX has finished, so we should go back to our KEX_NOTHING state */
167 if (cli_ses
.kex_state
!= KEX_NOTHING
&& ses
.kexstate
.recvkexinit
== 0
168 && ses
.kexstate
.sentkexinit
== 0) {
169 cli_ses
.kex_state
= KEX_NOTHING
;
172 /* We shouldn't do anything else if a KEX is in progress */
173 if (cli_ses
.kex_state
!= KEX_NOTHING
) {
174 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
178 /* We should exit if we haven't donefirstkex: we shouldn't reach here
179 * in normal operation */
180 if (ses
.kexstate
.donefirstkex
== 0) {
181 TRACE(("XXX XXX might be bad! leave cli_sessionloop: haven't donefirstkex"))
185 switch (cli_ses
.state
) {
188 /* We've got the transport layer sorted, we now need to request
190 send_msg_service_request(SSH_SERVICE_USERAUTH
);
191 cli_ses
.state
= SERVICE_AUTH_REQ_SENT
;
192 TRACE(("leave cli_sessionloop: sent userauth service req"))
196 case SERVICE_AUTH_ACCEPT_RCVD
:
197 cli_auth_getmethods();
198 cli_ses
.state
= USERAUTH_REQ_SENT
;
199 TRACE(("leave cli_sessionloop: sent userauth methods req"))
202 case USERAUTH_FAIL_RCVD
:
204 cli_ses
.state
= USERAUTH_REQ_SENT
;
205 TRACE(("leave cli_sessionloop: cli_auth_try"))
208 case USERAUTH_SUCCESS_RCVD
:
210 if (cli_opts
.backgrounded
) {
212 /* keeping stdin open steals input from the terminal and
213 is confusing, though stdout/stderr could be useful. */
214 devnull
= open(_PATH_DEVNULL
, O_RDONLY
);
216 dropbear_exit("Opening /dev/null: %d %s",
217 errno
, strerror(errno
));
219 dup2(devnull
, STDIN_FILENO
);
220 if (daemon(0, 1) < 0) {
221 dropbear_exit("Backgrounding failed: %d %s",
222 errno
, strerror(errno
));
226 #ifdef ENABLE_CLI_LOCALTCPFWD
229 #ifdef ENABLE_CLI_REMOTETCPFWD
233 #ifdef ENABLE_CLI_NETCAT
234 if (cli_opts
.netcat_host
) {
235 cli_send_netcat_request();
238 if (!cli_opts
.no_cmd
) {
239 cli_send_chansess_request();
241 TRACE(("leave cli_sessionloop: running"))
242 cli_ses
.state
= SESSION_RUNNING
;
245 case SESSION_RUNNING
:
246 if (ses
.chancount
< 1 && !cli_opts
.no_cmd
) {
250 if (cli_ses
.winchange
) {
251 cli_chansess_winchange();
255 /* XXX more here needed */
262 TRACE(("leave cli_sessionloop: fell out"))
266 void cli_session_cleanup() {
272 /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
273 * we don't revert the flags */
274 fcntl(cli_ses
.stdincopy
, F_SETFL
, cli_ses
.stdinflags
);
275 fcntl(cli_ses
.stdoutcopy
, F_SETFL
, cli_ses
.stdoutflags
);
276 fcntl(cli_ses
.stderrcopy
, F_SETFL
, cli_ses
.stderrflags
);
282 static void cli_finished() {
284 cli_session_cleanup();
285 common_session_cleanup();
286 fprintf(stderr
, "Connection to %s@%s:%s closed.\n", cli_opts
.username
,
287 cli_opts
.remotehost
, cli_opts
.remoteport
);
288 exit(cli_ses
.retval
);
292 /* called when the remote side closes the connection */
293 static void cli_remoteclosed() {
295 /* XXX TODO perhaps print a friendlier message if we get this but have
296 * already sent/received disconnect message(s) ??? */
297 m_close(ses
.sock_in
);
298 m_close(ses
.sock_out
);
301 dropbear_exit("Remote closed the connection");
304 /* Operates in-place turning dirty (untrusted potentially containing control
305 * characters) text into clean text.
306 * Note: this is safe only with ascii - other charsets could have problems. */
307 void cleantext(unsigned char* dirtytext
) {
313 for (i
= 0; dirtytext
[i
] != '\0'; i
++) {
316 /* We can ignore '\r's */
317 if ( (c
>= ' ' && c
<= '~') || c
== '\n' || c
== '\t') {