Mercurial > dropbear
comparison common-session.c @ 285:1b9e69c058d2
propagate from branch 'au.asn.ucc.matt.ltc.dropbear' (head 20dccfc09627970a312d77fb41dc2970b62689c3)
to branch 'au.asn.ucc.matt.dropbear' (head fdf4a7a3b97ae5046139915de7e40399cceb2c01)
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 08 Mar 2006 13:23:58 +0000 |
parents | 7f9adaf85fca |
children | bf29e6659fb9 |
comparison
equal
deleted
inserted
replaced
281:997e6f7dc01e | 285:1b9e69c058d2 |
---|---|
1 /* | |
2 * Dropbear - a SSH2 server | |
3 * | |
4 * Copyright (c) 2002,2003 Matt Johnston | |
5 * All rights reserved. | |
6 * | |
7 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 * of this software and associated documentation files (the "Software"), to deal | |
9 * in the Software without restriction, including without limitation the rights | |
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 * copies of the Software, and to permit persons to whom the Software is | |
12 * furnished to do so, subject to the following conditions: | |
13 * | |
14 * The above copyright notice and this permission notice shall be included in | |
15 * all copies or substantial portions of the Software. | |
16 * | |
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
23 * SOFTWARE. */ | |
24 | |
25 #include "includes.h" | |
26 #include "session.h" | |
27 #include "dbutil.h" | |
28 #include "packet.h" | |
29 #include "algo.h" | |
30 #include "buffer.h" | |
31 #include "dss.h" | |
32 #include "ssh.h" | |
33 #include "random.h" | |
34 #include "kex.h" | |
35 #include "channel.h" | |
36 #include "atomicio.h" | |
37 | |
38 static void checktimeouts(); | |
39 static int ident_readln(int fd, char* buf, int count); | |
40 | |
41 struct sshsession ses; /* GLOBAL */ | |
42 | |
43 /* need to know if the session struct has been initialised, this way isn't the | |
44 * cleanest, but works OK */ | |
45 int sessinitdone = 0; /* GLOBAL */ | |
46 | |
47 /* this is set when we get SIGINT or SIGTERM, the handler is in main.c */ | |
48 int exitflag = 0; /* GLOBAL */ | |
49 | |
50 | |
51 | |
52 /* called only at the start of a session, set up initial state */ | |
53 void common_session_init(int sock, char* remotehost) { | |
54 | |
55 TRACE(("enter session_init")) | |
56 | |
57 ses.remotehost = remotehost; | |
58 | |
59 ses.sock = sock; | |
60 ses.maxfd = sock; | |
61 | |
62 ses.connecttimeout = 0; | |
63 | |
64 kexfirstinitialise(); /* initialise the kex state */ | |
65 | |
66 ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN); | |
67 ses.transseq = 0; | |
68 | |
69 ses.readbuf = NULL; | |
70 ses.decryptreadbuf = NULL; | |
71 ses.payload = NULL; | |
72 ses.recvseq = 0; | |
73 | |
74 initqueue(&ses.writequeue); | |
75 | |
76 ses.requirenext = SSH_MSG_KEXINIT; | |
77 ses.dataallowed = 0; /* don't send data yet, we'll wait until after kex */ | |
78 ses.ignorenext = 0; | |
79 ses.lastpacket = 0; | |
80 | |
81 /* set all the algos to none */ | |
82 ses.keys = (struct key_context*)m_malloc(sizeof(struct key_context)); | |
83 ses.newkeys = NULL; | |
84 ses.keys->recv_algo_crypt = &dropbear_nocipher; | |
85 ses.keys->trans_algo_crypt = &dropbear_nocipher; | |
86 | |
87 ses.keys->recv_algo_mac = &dropbear_nohash; | |
88 ses.keys->trans_algo_mac = &dropbear_nohash; | |
89 | |
90 ses.keys->algo_kex = -1; | |
91 ses.keys->algo_hostkey = -1; | |
92 ses.keys->recv_algo_comp = DROPBEAR_COMP_NONE; | |
93 ses.keys->trans_algo_comp = DROPBEAR_COMP_NONE; | |
94 | |
95 #ifndef DISABLE_ZLIB | |
96 ses.keys->recv_zstream = NULL; | |
97 ses.keys->trans_zstream = NULL; | |
98 #endif | |
99 | |
100 /* key exchange buffers */ | |
101 ses.session_id = NULL; | |
102 ses.kexhashbuf = NULL; | |
103 ses.transkexinit = NULL; | |
104 ses.dh_K = NULL; | |
105 ses.remoteident = NULL; | |
106 | |
107 ses.chantypes = NULL; | |
108 | |
109 ses.allowprivport = 0; | |
110 | |
111 | |
112 TRACE(("leave session_init")) | |
113 } | |
114 | |
115 void session_loop(void(*loophandler)()) { | |
116 | |
117 fd_set readfd, writefd; | |
118 struct timeval timeout; | |
119 int val; | |
120 | |
121 /* main loop, select()s for all sockets in use */ | |
122 for(;;) { | |
123 | |
124 timeout.tv_sec = SELECT_TIMEOUT; | |
125 timeout.tv_usec = 0; | |
126 FD_ZERO(&writefd); | |
127 FD_ZERO(&readfd); | |
128 dropbear_assert(ses.payload == NULL); | |
129 if (ses.sock != -1) { | |
130 FD_SET(ses.sock, &readfd); | |
131 if (!isempty(&ses.writequeue)) { | |
132 FD_SET(ses.sock, &writefd); | |
133 } | |
134 } | |
135 | |
136 /* set up for channels which require reading/writing */ | |
137 if (ses.dataallowed) { | |
138 setchannelfds(&readfd, &writefd); | |
139 } | |
140 val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout); | |
141 | |
142 if (exitflag) { | |
143 dropbear_exit("Terminated by signal"); | |
144 } | |
145 | |
146 if (val < 0) { | |
147 if (errno == EINTR) { | |
148 /* This must happen even if we've been interrupted, so that | |
149 * changed signal-handler vars can take effect etc */ | |
150 if (loophandler) { | |
151 loophandler(); | |
152 } | |
153 continue; | |
154 } else { | |
155 dropbear_exit("Error in select"); | |
156 } | |
157 } | |
158 | |
159 /* check for auth timeout, rekeying required etc */ | |
160 checktimeouts(); | |
161 | |
162 if (val == 0) { | |
163 /* timeout */ | |
164 TRACE(("select timeout")) | |
165 continue; | |
166 } | |
167 | |
168 /* process session socket's incoming/outgoing data */ | |
169 if (ses.sock != -1) { | |
170 if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) { | |
171 write_packet(); | |
172 } | |
173 | |
174 if (FD_ISSET(ses.sock, &readfd)) { | |
175 read_packet(); | |
176 } | |
177 | |
178 /* Process the decrypted packet. After this, the read buffer | |
179 * will be ready for a new packet */ | |
180 if (ses.payload != NULL) { | |
181 process_packet(); | |
182 } | |
183 } | |
184 | |
185 /* process pipes etc for the channels, ses.dataallowed == 0 | |
186 * during rekeying ) */ | |
187 if (ses.dataallowed) { | |
188 channelio(&readfd, &writefd); | |
189 } | |
190 | |
191 if (loophandler) { | |
192 loophandler(); | |
193 } | |
194 | |
195 } /* for(;;) */ | |
196 | |
197 /* Not reached */ | |
198 } | |
199 | |
200 /* clean up a session on exit */ | |
201 void common_session_cleanup() { | |
202 | |
203 TRACE(("enter session_cleanup")) | |
204 | |
205 /* we can't cleanup if we don't know the session state */ | |
206 if (!sessinitdone) { | |
207 TRACE(("leave session_cleanup: !sessinitdone")) | |
208 return; | |
209 } | |
210 | |
211 m_free(ses.session_id); | |
212 m_burn(ses.keys, sizeof(struct key_context)); | |
213 m_free(ses.keys); | |
214 | |
215 chancleanup(); | |
216 | |
217 TRACE(("leave session_cleanup")) | |
218 } | |
219 | |
220 | |
221 void session_identification() { | |
222 | |
223 /* max length of 255 chars */ | |
224 char linebuf[256]; | |
225 int len = 0; | |
226 char done = 0; | |
227 int i; | |
228 | |
229 /* write our version string, this blocks */ | |
230 if (atomicio(write, ses.sock, LOCAL_IDENT "\r\n", | |
231 strlen(LOCAL_IDENT "\r\n")) == DROPBEAR_FAILURE) { | |
232 dropbear_exit("Error writing ident string"); | |
233 } | |
234 | |
235 /* If they send more than 50 lines, something is wrong */ | |
236 for (i = 0; i < 50; i++) { | |
237 len = ident_readln(ses.sock, linebuf, sizeof(linebuf)); | |
238 | |
239 if (len < 0 && errno != EINTR) { | |
240 /* It failed */ | |
241 break; | |
242 } | |
243 | |
244 if (len >= 4 && memcmp(linebuf, "SSH-", 4) == 0) { | |
245 /* start of line matches */ | |
246 done = 1; | |
247 break; | |
248 } | |
249 } | |
250 | |
251 if (!done) { | |
252 TRACE(("err: %s for '%s'\n", strerror(errno), linebuf)) | |
253 dropbear_exit("Failed to get remote version"); | |
254 } else { | |
255 /* linebuf is already null terminated */ | |
256 ses.remoteident = m_malloc(len); | |
257 memcpy(ses.remoteident, linebuf, len); | |
258 } | |
259 | |
260 /* Shall assume that 2.x will be backwards compatible. */ | |
261 if (strncmp(ses.remoteident, "SSH-2.", 6) != 0 | |
262 && strncmp(ses.remoteident, "SSH-1.99-", 9) != 0) { | |
263 dropbear_exit("Incompatible remote version '%s'", ses.remoteident); | |
264 } | |
265 | |
266 TRACE(("remoteident: %s", ses.remoteident)) | |
267 | |
268 } | |
269 | |
270 /* returns the length including null-terminating zero on success, | |
271 * or -1 on failure */ | |
272 static int ident_readln(int fd, char* buf, int count) { | |
273 | |
274 char in; | |
275 int pos = 0; | |
276 int num = 0; | |
277 fd_set fds; | |
278 struct timeval timeout; | |
279 | |
280 TRACE(("enter ident_readln")) | |
281 | |
282 if (count < 1) { | |
283 return -1; | |
284 } | |
285 | |
286 FD_ZERO(&fds); | |
287 | |
288 /* select since it's a non-blocking fd */ | |
289 | |
290 /* leave space to null-terminate */ | |
291 while (pos < count-1) { | |
292 | |
293 FD_SET(fd, &fds); | |
294 | |
295 timeout.tv_sec = 1; | |
296 timeout.tv_usec = 0; | |
297 if (select(fd+1, &fds, NULL, NULL, &timeout) < 0) { | |
298 if (errno == EINTR) { | |
299 continue; | |
300 } | |
301 TRACE(("leave ident_readln: select error")) | |
302 return -1; | |
303 } | |
304 | |
305 checktimeouts(); | |
306 | |
307 /* Have to go one byte at a time, since we don't want to read past | |
308 * the end, and have to somehow shove bytes back into the normal | |
309 * packet reader */ | |
310 if (FD_ISSET(fd, &fds)) { | |
311 num = read(fd, &in, 1); | |
312 /* a "\n" is a newline, "\r" we want to read in and keep going | |
313 * so that it won't be read as part of the next line */ | |
314 if (num < 0) { | |
315 /* error */ | |
316 if (errno == EINTR) { | |
317 continue; /* not a real error */ | |
318 } | |
319 TRACE(("leave ident_readln: read error")) | |
320 return -1; | |
321 } | |
322 if (num == 0) { | |
323 /* EOF */ | |
324 TRACE(("leave ident_readln: EOF")) | |
325 return -1; | |
326 } | |
327 if (in == '\n') { | |
328 /* end of ident string */ | |
329 break; | |
330 } | |
331 /* we don't want to include '\r's */ | |
332 if (in != '\r') { | |
333 buf[pos] = in; | |
334 pos++; | |
335 } | |
336 } | |
337 } | |
338 | |
339 buf[pos] = '\0'; | |
340 TRACE(("leave ident_readln: return %d", pos+1)) | |
341 return pos+1; | |
342 } | |
343 | |
344 /* Check all timeouts which are required. Currently these are the time for | |
345 * user authentication, and the automatic rekeying. */ | |
346 static void checktimeouts() { | |
347 | |
348 struct timeval tv; | |
349 long secs; | |
350 | |
351 if (gettimeofday(&tv, 0) < 0) { | |
352 dropbear_exit("Error getting time"); | |
353 } | |
354 | |
355 secs = tv.tv_sec; | |
356 | |
357 if (ses.connecttimeout != 0 && secs > ses.connecttimeout) { | |
358 dropbear_close("Timeout before auth"); | |
359 } | |
360 | |
361 /* we can't rekey if we haven't done remote ident exchange yet */ | |
362 if (ses.remoteident == NULL) { | |
363 return; | |
364 } | |
365 | |
366 if (!ses.kexstate.sentkexinit | |
367 && (secs - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT | |
368 || ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)){ | |
369 TRACE(("rekeying after timeout or max data reached")) | |
370 send_msg_kexinit(); | |
371 } | |
372 } | |
373 |