Mercurial > dropbear
comparison cli-session.c @ 391:00fcf5045160
propagate from branch 'au.asn.ucc.matt.ltc.dropbear' (head c1db4398d56c56c6d06ae1e20c1e0d04dbb598ed)
to branch 'au.asn.ucc.matt.dropbear' (head d26d5eb2837f46b56a33fb0e7573aa0201abd4d5)
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Thu, 11 Jan 2007 04:29:08 +0000 |
parents | d965110e3f5c |
children | c216212001fc |
comparison
equal
deleted
inserted
replaced
390:d8e44bef7917 | 391:00fcf5045160 |
---|---|
1 /* | |
2 * Dropbear SSH | |
3 * | |
4 * Copyright (c) 2002,2003 Matt Johnston | |
5 * Copyright (c) 2004 by Mihnea Stoenescu | |
6 * All rights reserved. | |
7 * | |
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: | |
14 * | |
15 * The above copyright notice and this permission notice shall be included in | |
16 * all copies or substantial portions of the Software. | |
17 * | |
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 | |
24 * SOFTWARE. */ | |
25 | |
26 #include "includes.h" | |
27 #include "session.h" | |
28 #include "dbutil.h" | |
29 #include "kex.h" | |
30 #include "ssh.h" | |
31 #include "packet.h" | |
32 #include "tcpfwd.h" | |
33 #include "channel.h" | |
34 #include "random.h" | |
35 #include "service.h" | |
36 #include "runopts.h" | |
37 #include "chansession.h" | |
38 | |
39 static void cli_remoteclosed(); | |
40 static void cli_sessionloop(); | |
41 static void cli_session_init(); | |
42 static void cli_finished(); | |
43 | |
44 struct clientsession cli_ses; /* GLOBAL */ | |
45 | |
46 /* Sorted in decreasing frequency will be more efficient - data and window | |
47 * should be first */ | |
48 static const packettype cli_packettypes[] = { | |
49 /* TYPE, FUNCTION */ | |
50 {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data}, | |
51 {SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data}, | |
52 {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust}, | |
53 {SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */ | |
54 {SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */ | |
55 {SSH_MSG_KEXINIT, recv_msg_kexinit}, | |
56 {SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */ | |
57 {SSH_MSG_NEWKEYS, recv_msg_newkeys}, | |
58 {SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */ | |
59 {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request}, | |
60 {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open}, | |
61 {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof}, | |
62 {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close}, | |
63 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, | |
64 {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, | |
65 {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ | |
66 {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ | |
67 {0, 0} /* End */ | |
68 }; | |
69 | |
70 static const struct ChanType *cli_chantypes[] = { | |
71 #ifdef ENABLE_CLI_REMOTETCPFWD | |
72 &cli_chan_tcpremote, | |
73 #endif | |
74 NULL /* Null termination */ | |
75 }; | |
76 | |
77 void cli_session(int sock, char* remotehost) { | |
78 | |
79 seedrandom(); | |
80 | |
81 crypto_init(); | |
82 | |
83 common_session_init(sock, remotehost); | |
84 | |
85 chaninitialise(cli_chantypes); | |
86 | |
87 /* Set up cli_ses vars */ | |
88 cli_session_init(); | |
89 | |
90 /* Ready to go */ | |
91 sessinitdone = 1; | |
92 | |
93 /* Exchange identification */ | |
94 session_identification(); | |
95 | |
96 send_msg_kexinit(); | |
97 | |
98 session_loop(cli_sessionloop); | |
99 | |
100 /* Not reached */ | |
101 | |
102 } | |
103 | |
104 static void cli_session_init() { | |
105 | |
106 cli_ses.state = STATE_NOTHING; | |
107 cli_ses.kex_state = KEX_NOTHING; | |
108 | |
109 cli_ses.tty_raw_mode = 0; | |
110 cli_ses.winchange = 0; | |
111 | |
112 /* We store std{in,out,err}'s flags, so we can set them back on exit | |
113 * (otherwise busybox's ash isn't happy */ | |
114 cli_ses.stdincopy = dup(STDIN_FILENO); | |
115 cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0); | |
116 cli_ses.stdoutcopy = dup(STDOUT_FILENO); | |
117 cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0); | |
118 cli_ses.stderrcopy = dup(STDERR_FILENO); | |
119 cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0); | |
120 | |
121 cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a | |
122 specific exit status */ | |
123 | |
124 /* Auth */ | |
125 cli_ses.lastprivkey = NULL; | |
126 cli_ses.lastauthtype = 0; | |
127 | |
128 /* For printing "remote host closed" for the user */ | |
129 ses.remoteclosed = cli_remoteclosed; | |
130 ses.buf_match_algo = cli_buf_match_algo; | |
131 | |
132 /* packet handlers */ | |
133 ses.packettypes = cli_packettypes; | |
134 | |
135 ses.isserver = 0; | |
136 } | |
137 | |
138 /* This function drives the progress of the session - it initiates KEX, | |
139 * service, userauth and channel requests */ | |
140 static void cli_sessionloop() { | |
141 | |
142 TRACE(("enter cli_sessionloop")) | |
143 | |
144 if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) { | |
145 cli_ses.kex_state = KEXINIT_RCVD; | |
146 } | |
147 | |
148 if (cli_ses.kex_state == KEXINIT_RCVD) { | |
149 | |
150 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT | |
151 * negotiation would have failed. */ | |
152 send_msg_kexdh_init(); | |
153 cli_ses.kex_state = KEXDH_INIT_SENT; | |
154 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD")) | |
155 return; | |
156 } | |
157 | |
158 /* A KEX has finished, so we should go back to our KEX_NOTHING state */ | |
159 if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.recvkexinit == 0 | |
160 && ses.kexstate.sentkexinit == 0) { | |
161 cli_ses.kex_state = KEX_NOTHING; | |
162 } | |
163 | |
164 /* We shouldn't do anything else if a KEX is in progress */ | |
165 if (cli_ses.kex_state != KEX_NOTHING) { | |
166 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING")) | |
167 return; | |
168 } | |
169 | |
170 /* We should exit if we haven't donefirstkex: we shouldn't reach here | |
171 * in normal operation */ | |
172 if (ses.kexstate.donefirstkex == 0) { | |
173 TRACE(("XXX XXX might be bad! leave cli_sessionloop: haven't donefirstkex")) | |
174 return; | |
175 } | |
176 | |
177 switch (cli_ses.state) { | |
178 | |
179 case STATE_NOTHING: | |
180 /* We've got the transport layer sorted, we now need to request | |
181 * userauth */ | |
182 send_msg_service_request(SSH_SERVICE_USERAUTH); | |
183 cli_ses.state = SERVICE_AUTH_REQ_SENT; | |
184 TRACE(("leave cli_sessionloop: sent userauth service req")) | |
185 return; | |
186 | |
187 /* userauth code */ | |
188 case SERVICE_AUTH_ACCEPT_RCVD: | |
189 cli_auth_getmethods(); | |
190 cli_ses.state = USERAUTH_REQ_SENT; | |
191 TRACE(("leave cli_sessionloop: sent userauth methods req")) | |
192 return; | |
193 | |
194 case USERAUTH_FAIL_RCVD: | |
195 cli_auth_try(); | |
196 cli_ses.state = USERAUTH_REQ_SENT; | |
197 TRACE(("leave cli_sessionloop: cli_auth_try")) | |
198 return; | |
199 | |
200 /* | |
201 case USERAUTH_SUCCESS_RCVD: | |
202 send_msg_service_request(SSH_SERVICE_CONNECTION); | |
203 cli_ses.state = SERVICE_CONN_REQ_SENT; | |
204 TRACE(("leave cli_sessionloop: sent ssh-connection service req")) | |
205 return; | |
206 | |
207 case SERVICE_CONN_ACCEPT_RCVD: | |
208 cli_send_chansess_request(); | |
209 TRACE(("leave cli_sessionloop: cli_send_chansess_request")) | |
210 cli_ses.state = SESSION_RUNNING; | |
211 return; | |
212 */ | |
213 | |
214 case USERAUTH_SUCCESS_RCVD: | |
215 | |
216 if (cli_opts.backgrounded) { | |
217 int devnull; | |
218 // keeping stdin open steals input from the terminal and | |
219 // is confusing, though stdout/stderr could be useful. | |
220 devnull = open(_PATH_DEVNULL, O_RDONLY); | |
221 if (devnull < 0) { | |
222 dropbear_exit("opening /dev/null: %d %s", | |
223 errno, strerror(errno)); | |
224 } | |
225 dup2(devnull, STDIN_FILENO); | |
226 if (daemon(0, 1) < 0) { | |
227 dropbear_exit("Backgrounding failed: %d %s", | |
228 errno, strerror(errno)); | |
229 } | |
230 } | |
231 | |
232 #ifdef ENABLE_CLI_LOCALTCPFWD | |
233 setup_localtcp(); | |
234 #endif | |
235 #ifdef ENABLE_CLI_REMOTETCPFWD | |
236 setup_remotetcp(); | |
237 #endif | |
238 if (!cli_opts.no_cmd) { | |
239 cli_send_chansess_request(); | |
240 } | |
241 TRACE(("leave cli_sessionloop: running")) | |
242 cli_ses.state = SESSION_RUNNING; | |
243 return; | |
244 | |
245 case SESSION_RUNNING: | |
246 if (ses.chancount < 1 && !cli_opts.no_cmd) { | |
247 cli_finished(); | |
248 } | |
249 | |
250 if (cli_ses.winchange) { | |
251 cli_chansess_winchange(); | |
252 } | |
253 return; | |
254 | |
255 /* XXX more here needed */ | |
256 | |
257 | |
258 default: | |
259 break; | |
260 } | |
261 | |
262 TRACE(("leave cli_sessionloop: fell out")) | |
263 | |
264 } | |
265 | |
266 void cli_session_cleanup() { | |
267 | |
268 if (!sessinitdone) { | |
269 return; | |
270 } | |
271 | |
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); | |
277 | |
278 cli_tty_cleanup(); | |
279 | |
280 } | |
281 | |
282 static void cli_finished() { | |
283 | |
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); | |
289 } | |
290 | |
291 | |
292 /* called when the remote side closes the connection */ | |
293 static void cli_remoteclosed() { | |
294 | |
295 /* XXX TODO perhaps print a friendlier message if we get this but have | |
296 * already sent/received disconnect message(s) ??? */ | |
297 close(ses.sock); | |
298 ses.sock = -1; | |
299 dropbear_exit("remote closed the connection"); | |
300 } | |
301 | |
302 /* Operates in-place turning dirty (untrusted potentially containing control | |
303 * characters) text into clean text. | |
304 * Note: this is safe only with ascii - other charsets could have problems. */ | |
305 void cleantext(unsigned char* dirtytext) { | |
306 | |
307 unsigned int i, j; | |
308 unsigned char c; | |
309 | |
310 j = 0; | |
311 for (i = 0; dirtytext[i] != '\0'; i++) { | |
312 | |
313 c = dirtytext[i]; | |
314 /* We can ignore '\r's */ | |
315 if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') { | |
316 dirtytext[j] = c; | |
317 j++; | |
318 } | |
319 } | |
320 /* Null terminate */ | |
321 dirtytext[j] = '\0'; | |
322 } |