Mercurial > dropbear
comparison svr-chansession.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 | 1f5ec029dfe8 |
children | 5d5bbca82aba 3eea61bd9993 dba106bf6b34 8eaa6e3ca6eb |
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 "packet.h" | |
27 #include "buffer.h" | |
28 #include "session.h" | |
29 #include "dbutil.h" | |
30 #include "channel.h" | |
31 #include "chansession.h" | |
32 #include "sshpty.h" | |
33 #include "termcodes.h" | |
34 #include "ssh.h" | |
35 #include "random.h" | |
36 #include "utmp.h" | |
37 #include "x11fwd.h" | |
38 #include "agentfwd.h" | |
39 #include "runopts.h" | |
40 | |
41 /* Handles sessions (either shells or programs) requested by the client */ | |
42 | |
43 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, | |
44 int iscmd, int issubsys); | |
45 static int sessionpty(struct ChanSess * chansess); | |
46 static int sessionsignal(struct ChanSess *chansess); | |
47 static int noptycommand(struct Channel *channel, struct ChanSess *chansess); | |
48 static int ptycommand(struct Channel *channel, struct ChanSess *chansess); | |
49 static int sessionwinchange(struct ChanSess *chansess); | |
50 static void execchild(struct ChanSess *chansess); | |
51 static void addchildpid(struct ChanSess *chansess, pid_t pid); | |
52 static void sesssigchild_handler(int val); | |
53 static void closechansess(struct Channel *channel); | |
54 static int newchansess(struct Channel *channel); | |
55 static void chansessionrequest(struct Channel *channel); | |
56 | |
57 static void send_exitsignalstatus(struct Channel *channel); | |
58 static void send_msg_chansess_exitstatus(struct Channel * channel, | |
59 struct ChanSess * chansess); | |
60 static void send_msg_chansess_exitsignal(struct Channel * channel, | |
61 struct ChanSess * chansess); | |
62 static int sesscheckclose(struct Channel *channel); | |
63 static void get_termmodes(struct ChanSess *chansess); | |
64 | |
65 | |
66 /* required to clear environment */ | |
67 extern char** environ; | |
68 | |
69 static int sesscheckclose(struct Channel *channel) { | |
70 struct ChanSess *chansess = (struct ChanSess*)channel->typedata; | |
71 return chansess->exit.exitpid >= 0; | |
72 } | |
73 | |
74 /* Handler for childs exiting, store the state for return to the client */ | |
75 | |
76 /* There's a particular race we have to watch out for: if the forked child | |
77 * executes, exits, and this signal-handler is called, all before the parent | |
78 * gets to run, then the childpids[] array won't have the pid in it. Hence we | |
79 * use the svr_ses.lastexit struct to hold the exit, which is then compared by | |
80 * the parent when it runs. This work correctly at least in the case of a | |
81 * single shell spawned (ie the usual case) */ | |
82 static void sesssigchild_handler(int UNUSED(dummy)) { | |
83 | |
84 int status; | |
85 pid_t pid; | |
86 unsigned int i; | |
87 struct sigaction sa_chld; | |
88 struct exitinfo *exit = NULL; | |
89 | |
90 TRACE(("enter sigchld handler")) | |
91 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { | |
92 /* find the corresponding chansess */ | |
93 for (i = 0; i < svr_ses.childpidsize; i++) { | |
94 if (svr_ses.childpids[i].pid == pid) { | |
95 | |
96 exit = &svr_ses.childpids[i].chansess->exit; | |
97 break; | |
98 } | |
99 } | |
100 | |
101 /* If the pid wasn't matched, then we might have hit the race mentioned | |
102 * above. So we just store the info for the parent to deal with */ | |
103 if (i == svr_ses.childpidsize) { | |
104 exit = &svr_ses.lastexit; | |
105 } | |
106 | |
107 exit->exitpid = pid; | |
108 if (WIFEXITED(status)) { | |
109 exit->exitstatus = WEXITSTATUS(status); | |
110 } | |
111 if (WIFSIGNALED(status)) { | |
112 exit->exitsignal = WTERMSIG(status); | |
113 #if !defined(AIX) && defined(WCOREDUMP) | |
114 exit->exitcore = WCOREDUMP(status); | |
115 #else | |
116 exit->exitcore = 0; | |
117 #endif | |
118 } else { | |
119 /* we use this to determine how pid exited */ | |
120 exit->exitsignal = -1; | |
121 } | |
122 exit = NULL; | |
123 } | |
124 | |
125 | |
126 sa_chld.sa_handler = sesssigchild_handler; | |
127 sa_chld.sa_flags = SA_NOCLDSTOP; | |
128 sigaction(SIGCHLD, &sa_chld, NULL); | |
129 TRACE(("leave sigchld handler")) | |
130 } | |
131 | |
132 /* send the exit status or the signal causing termination for a session */ | |
133 /* XXX server */ | |
134 static void send_exitsignalstatus(struct Channel *channel) { | |
135 | |
136 struct ChanSess *chansess = (struct ChanSess*)channel->typedata; | |
137 | |
138 if (chansess->exit.exitpid >= 0) { | |
139 if (chansess->exit.exitsignal > 0) { | |
140 send_msg_chansess_exitsignal(channel, chansess); | |
141 } else { | |
142 send_msg_chansess_exitstatus(channel, chansess); | |
143 } | |
144 } | |
145 } | |
146 | |
147 /* send the exitstatus to the client */ | |
148 static void send_msg_chansess_exitstatus(struct Channel * channel, | |
149 struct ChanSess * chansess) { | |
150 | |
151 dropbear_assert(chansess->exit.exitpid != -1); | |
152 dropbear_assert(chansess->exit.exitsignal == -1); | |
153 | |
154 CHECKCLEARTOWRITE(); | |
155 | |
156 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); | |
157 buf_putint(ses.writepayload, channel->remotechan); | |
158 buf_putstring(ses.writepayload, "exit-status", 11); | |
159 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */ | |
160 buf_putint(ses.writepayload, chansess->exit.exitstatus); | |
161 | |
162 encrypt_packet(); | |
163 | |
164 } | |
165 | |
166 /* send the signal causing the exit to the client */ | |
167 static void send_msg_chansess_exitsignal(struct Channel * channel, | |
168 struct ChanSess * chansess) { | |
169 | |
170 int i; | |
171 char* signame = NULL; | |
172 | |
173 dropbear_assert(chansess->exit.exitpid != -1); | |
174 dropbear_assert(chansess->exit.exitsignal > 0); | |
175 | |
176 CHECKCLEARTOWRITE(); | |
177 | |
178 /* we check that we can match a signal name, otherwise | |
179 * don't send anything */ | |
180 for (i = 0; signames[i].name != NULL; i++) { | |
181 if (signames[i].signal == chansess->exit.exitsignal) { | |
182 signame = signames[i].name; | |
183 break; | |
184 } | |
185 } | |
186 | |
187 if (signame == NULL) { | |
188 return; | |
189 } | |
190 | |
191 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); | |
192 buf_putint(ses.writepayload, channel->remotechan); | |
193 buf_putstring(ses.writepayload, "exit-signal", 11); | |
194 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */ | |
195 buf_putstring(ses.writepayload, signame, strlen(signame)); | |
196 buf_putbyte(ses.writepayload, chansess->exit.exitcore); | |
197 buf_putstring(ses.writepayload, "", 0); /* error msg */ | |
198 buf_putstring(ses.writepayload, "", 0); /* lang */ | |
199 | |
200 encrypt_packet(); | |
201 } | |
202 | |
203 /* set up a session channel */ | |
204 static int newchansess(struct Channel *channel) { | |
205 | |
206 struct ChanSess *chansess; | |
207 | |
208 dropbear_assert(channel->typedata == NULL); | |
209 | |
210 chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess)); | |
211 chansess->cmd = NULL; | |
212 chansess->pid = 0; | |
213 | |
214 /* pty details */ | |
215 chansess->master = -1; | |
216 chansess->slave = -1; | |
217 chansess->tty = NULL; | |
218 chansess->term = NULL; | |
219 | |
220 chansess->exit.exitpid = -1; | |
221 | |
222 channel->typedata = chansess; | |
223 | |
224 #ifndef DISABLE_X11FWD | |
225 chansess->x11listener = NULL; | |
226 chansess->x11authprot = NULL; | |
227 chansess->x11authcookie = NULL; | |
228 #endif | |
229 | |
230 #ifndef DISABLE_AGENTFWD | |
231 chansess->agentlistener = NULL; | |
232 chansess->agentfile = NULL; | |
233 chansess->agentdir = NULL; | |
234 #endif | |
235 | |
236 return 0; | |
237 | |
238 } | |
239 | |
240 /* clean a session channel */ | |
241 static void closechansess(struct Channel *channel) { | |
242 | |
243 struct ChanSess *chansess; | |
244 unsigned int i; | |
245 struct logininfo *li; | |
246 | |
247 chansess = (struct ChanSess*)channel->typedata; | |
248 | |
249 send_exitsignalstatus(channel); | |
250 | |
251 TRACE(("enter closechansess")) | |
252 if (chansess == NULL) { | |
253 TRACE(("leave closechansess: chansess == NULL")) | |
254 return; | |
255 } | |
256 | |
257 m_free(chansess->cmd); | |
258 m_free(chansess->term); | |
259 | |
260 if (chansess->tty) { | |
261 /* write the utmp/wtmp login record */ | |
262 li = login_alloc_entry(chansess->pid, ses.authstate.username, | |
263 ses.remotehost, chansess->tty); | |
264 login_logout(li); | |
265 login_free_entry(li); | |
266 | |
267 pty_release(chansess->tty); | |
268 m_free(chansess->tty); | |
269 } | |
270 | |
271 #ifndef DISABLE_X11FWD | |
272 x11cleanup(chansess); | |
273 #endif | |
274 | |
275 #ifndef DISABLE_AGENTFWD | |
276 agentcleanup(chansess); | |
277 #endif | |
278 | |
279 /* clear child pid entries */ | |
280 for (i = 0; i < svr_ses.childpidsize; i++) { | |
281 if (svr_ses.childpids[i].chansess == chansess) { | |
282 dropbear_assert(svr_ses.childpids[i].pid > 0); | |
283 TRACE(("closing pid %d", svr_ses.childpids[i].pid)) | |
284 TRACE(("exitpid = %d", chansess->exit.exitpid)) | |
285 svr_ses.childpids[i].pid = -1; | |
286 svr_ses.childpids[i].chansess = NULL; | |
287 } | |
288 } | |
289 | |
290 m_free(chansess); | |
291 | |
292 TRACE(("leave closechansess")) | |
293 } | |
294 | |
295 /* Handle requests for a channel. These can be execution requests, | |
296 * or x11/authagent forwarding. These are passed to appropriate handlers */ | |
297 static void chansessionrequest(struct Channel *channel) { | |
298 | |
299 unsigned char * type = NULL; | |
300 unsigned int typelen; | |
301 unsigned char wantreply; | |
302 int ret = 1; | |
303 struct ChanSess *chansess; | |
304 | |
305 TRACE(("enter chansessionrequest")) | |
306 | |
307 type = buf_getstring(ses.payload, &typelen); | |
308 wantreply = buf_getbool(ses.payload); | |
309 | |
310 if (typelen > MAX_NAME_LEN) { | |
311 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/ | |
312 goto out; | |
313 } | |
314 | |
315 chansess = (struct ChanSess*)channel->typedata; | |
316 dropbear_assert(chansess != NULL); | |
317 TRACE(("type is %s", type)) | |
318 | |
319 if (strcmp(type, "window-change") == 0) { | |
320 ret = sessionwinchange(chansess); | |
321 } else if (strcmp(type, "shell") == 0) { | |
322 ret = sessioncommand(channel, chansess, 0, 0); | |
323 } else if (strcmp(type, "pty-req") == 0) { | |
324 ret = sessionpty(chansess); | |
325 } else if (strcmp(type, "exec") == 0) { | |
326 ret = sessioncommand(channel, chansess, 1, 0); | |
327 } else if (strcmp(type, "subsystem") == 0) { | |
328 ret = sessioncommand(channel, chansess, 1, 1); | |
329 #ifndef DISABLE_X11FWD | |
330 } else if (strcmp(type, "x11-req") == 0) { | |
331 ret = x11req(chansess); | |
332 #endif | |
333 #ifndef DISABLE_AGENTFWD | |
334 } else if (strcmp(type, "[email protected]") == 0) { | |
335 ret = agentreq(chansess); | |
336 #endif | |
337 } else if (strcmp(type, "signal") == 0) { | |
338 ret = sessionsignal(chansess); | |
339 } else { | |
340 /* etc, todo "env", "subsystem" */ | |
341 } | |
342 | |
343 out: | |
344 | |
345 if (wantreply) { | |
346 if (ret == DROPBEAR_SUCCESS) { | |
347 send_msg_channel_success(channel); | |
348 } else { | |
349 send_msg_channel_failure(channel); | |
350 } | |
351 } | |
352 | |
353 m_free(type); | |
354 TRACE(("leave chansessionrequest")) | |
355 } | |
356 | |
357 | |
358 /* Send a signal to a session's process as requested by the client*/ | |
359 static int sessionsignal(struct ChanSess *chansess) { | |
360 | |
361 int sig = 0; | |
362 unsigned char* signame = NULL; | |
363 int i; | |
364 | |
365 if (chansess->pid == 0) { | |
366 /* haven't got a process pid yet */ | |
367 return DROPBEAR_FAILURE; | |
368 } | |
369 | |
370 signame = buf_getstring(ses.payload, NULL); | |
371 | |
372 i = 0; | |
373 while (signames[i].name != 0) { | |
374 if (strcmp(signames[i].name, signame) == 0) { | |
375 sig = signames[i].signal; | |
376 break; | |
377 } | |
378 i++; | |
379 } | |
380 | |
381 m_free(signame); | |
382 | |
383 if (sig == 0) { | |
384 /* failed */ | |
385 return DROPBEAR_FAILURE; | |
386 } | |
387 | |
388 if (kill(chansess->pid, sig) < 0) { | |
389 return DROPBEAR_FAILURE; | |
390 } | |
391 | |
392 return DROPBEAR_SUCCESS; | |
393 } | |
394 | |
395 /* Let the process know that the window size has changed, as notified from the | |
396 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ | |
397 static int sessionwinchange(struct ChanSess *chansess) { | |
398 | |
399 int termc, termr, termw, termh; | |
400 | |
401 if (chansess->master < 0) { | |
402 /* haven't got a pty yet */ | |
403 return DROPBEAR_FAILURE; | |
404 } | |
405 | |
406 termc = buf_getint(ses.payload); | |
407 termr = buf_getint(ses.payload); | |
408 termw = buf_getint(ses.payload); | |
409 termh = buf_getint(ses.payload); | |
410 | |
411 pty_change_window_size(chansess->master, termr, termc, termw, termh); | |
412 | |
413 return DROPBEAR_FAILURE; | |
414 } | |
415 | |
416 static void get_termmodes(struct ChanSess *chansess) { | |
417 | |
418 struct termios termio; | |
419 unsigned char opcode; | |
420 unsigned int value; | |
421 const struct TermCode * termcode; | |
422 unsigned int len; | |
423 | |
424 TRACE(("enter get_termmodes")) | |
425 | |
426 /* Term modes */ | |
427 /* We'll ignore errors and continue if we can't set modes. | |
428 * We're ignoring baud rates since they seem evil */ | |
429 if (tcgetattr(chansess->master, &termio) == -1) { | |
430 return; | |
431 } | |
432 | |
433 len = buf_getint(ses.payload); | |
434 TRACE(("term mode str %d p->l %d p->p %d", | |
435 len, ses.payload->len , ses.payload->pos)); | |
436 if (len != ses.payload->len - ses.payload->pos) { | |
437 dropbear_exit("bad term mode string"); | |
438 } | |
439 | |
440 if (len == 0) { | |
441 TRACE(("leave get_termmodes: empty terminal modes string")) | |
442 return; | |
443 } | |
444 | |
445 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) { | |
446 | |
447 /* must be before checking type, so that value is consumed even if | |
448 * we don't use it */ | |
449 value = buf_getint(ses.payload); | |
450 | |
451 /* handle types of code */ | |
452 if (opcode > MAX_TERMCODE) { | |
453 continue; | |
454 } | |
455 termcode = &termcodes[(unsigned int)opcode]; | |
456 | |
457 | |
458 switch (termcode->type) { | |
459 | |
460 case TERMCODE_NONE: | |
461 break; | |
462 | |
463 case TERMCODE_CONTROLCHAR: | |
464 termio.c_cc[termcode->mapcode] = value; | |
465 break; | |
466 | |
467 case TERMCODE_INPUT: | |
468 if (value) { | |
469 termio.c_iflag |= termcode->mapcode; | |
470 } else { | |
471 termio.c_iflag &= ~(termcode->mapcode); | |
472 } | |
473 break; | |
474 | |
475 case TERMCODE_OUTPUT: | |
476 if (value) { | |
477 termio.c_oflag |= termcode->mapcode; | |
478 } else { | |
479 termio.c_oflag &= ~(termcode->mapcode); | |
480 } | |
481 break; | |
482 | |
483 case TERMCODE_LOCAL: | |
484 if (value) { | |
485 termio.c_lflag |= termcode->mapcode; | |
486 } else { | |
487 termio.c_lflag &= ~(termcode->mapcode); | |
488 } | |
489 break; | |
490 | |
491 case TERMCODE_CONTROL: | |
492 if (value) { | |
493 termio.c_cflag |= termcode->mapcode; | |
494 } else { | |
495 termio.c_cflag &= ~(termcode->mapcode); | |
496 } | |
497 break; | |
498 | |
499 } | |
500 } | |
501 if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) { | |
502 dropbear_log(LOG_INFO, "error setting terminal attributes"); | |
503 } | |
504 TRACE(("leave get_termmodes")) | |
505 } | |
506 | |
507 /* Set up a session pty which will be used to execute the shell or program. | |
508 * The pty is allocated now, and kept for when the shell/program executes. | |
509 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ | |
510 static int sessionpty(struct ChanSess * chansess) { | |
511 | |
512 unsigned int termlen; | |
513 unsigned char namebuf[65]; | |
514 | |
515 TRACE(("enter sessionpty")) | |
516 chansess->term = buf_getstring(ses.payload, &termlen); | |
517 if (termlen > MAX_TERM_LEN) { | |
518 /* TODO send disconnect ? */ | |
519 TRACE(("leave sessionpty: term len too long")) | |
520 return DROPBEAR_FAILURE; | |
521 } | |
522 | |
523 /* allocate the pty */ | |
524 if (chansess->master != -1) { | |
525 dropbear_exit("multiple pty requests"); | |
526 } | |
527 if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) { | |
528 TRACE(("leave sessionpty: failed to allocate pty")) | |
529 return DROPBEAR_FAILURE; | |
530 } | |
531 | |
532 chansess->tty = (char*)m_strdup(namebuf); | |
533 if (!chansess->tty) { | |
534 dropbear_exit("out of memory"); /* TODO disconnect */ | |
535 } | |
536 | |
537 pty_setowner(ses.authstate.pw, chansess->tty); | |
538 | |
539 /* Set up the rows/col counts */ | |
540 sessionwinchange(chansess); | |
541 | |
542 /* Read the terminal modes */ | |
543 get_termmodes(chansess); | |
544 | |
545 TRACE(("leave sessionpty")) | |
546 return DROPBEAR_SUCCESS; | |
547 } | |
548 | |
549 /* Handle a command request from the client. This is used for both shell | |
550 * and command-execution requests, and passes the command to | |
551 * noptycommand or ptycommand as appropriate. | |
552 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ | |
553 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, | |
554 int iscmd, int issubsys) { | |
555 | |
556 unsigned int cmdlen; | |
557 int ret; | |
558 | |
559 TRACE(("enter sessioncommand")) | |
560 | |
561 if (chansess->cmd != NULL) { | |
562 /* Note that only one command can _succeed_. The client might try | |
563 * one command (which fails), then try another. Ie fallback | |
564 * from sftp to scp */ | |
565 return DROPBEAR_FAILURE; | |
566 } | |
567 | |
568 if (iscmd) { | |
569 /* "exec" */ | |
570 chansess->cmd = buf_getstring(ses.payload, &cmdlen); | |
571 | |
572 if (cmdlen > MAX_CMD_LEN) { | |
573 m_free(chansess->cmd); | |
574 /* TODO - send error - too long ? */ | |
575 return DROPBEAR_FAILURE; | |
576 } | |
577 if (issubsys) { | |
578 #ifdef SFTPSERVER_PATH | |
579 if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) { | |
580 m_free(chansess->cmd); | |
581 chansess->cmd = m_strdup(SFTPSERVER_PATH); | |
582 } else | |
583 #endif | |
584 { | |
585 m_free(chansess->cmd); | |
586 return DROPBEAR_FAILURE; | |
587 } | |
588 } | |
589 } | |
590 | |
591 if (chansess->term == NULL) { | |
592 /* no pty */ | |
593 ret = noptycommand(channel, chansess); | |
594 } else { | |
595 /* want pty */ | |
596 ret = ptycommand(channel, chansess); | |
597 } | |
598 | |
599 if (ret == DROPBEAR_FAILURE) { | |
600 m_free(chansess->cmd); | |
601 } | |
602 return ret; | |
603 } | |
604 | |
605 /* Execute a command and set up redirection of stdin/stdout/stderr without a | |
606 * pty. | |
607 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ | |
608 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) { | |
609 | |
610 int infds[2]; | |
611 int outfds[2]; | |
612 int errfds[2]; | |
613 pid_t pid; | |
614 unsigned int i; | |
615 | |
616 TRACE(("enter noptycommand")) | |
617 | |
618 /* redirect stdin/stdout/stderr */ | |
619 if (pipe(infds) != 0) | |
620 return DROPBEAR_FAILURE; | |
621 if (pipe(outfds) != 0) | |
622 return DROPBEAR_FAILURE; | |
623 if (pipe(errfds) != 0) | |
624 return DROPBEAR_FAILURE; | |
625 | |
626 #ifdef __uClinux__ | |
627 pid = vfork(); | |
628 #else | |
629 pid = fork(); | |
630 #endif | |
631 | |
632 if (pid < 0) | |
633 return DROPBEAR_FAILURE; | |
634 | |
635 if (!pid) { | |
636 /* child */ | |
637 | |
638 /* redirect stdin/stdout */ | |
639 #define FDIN 0 | |
640 #define FDOUT 1 | |
641 if ((dup2(infds[FDIN], STDIN_FILENO) < 0) || | |
642 (dup2(outfds[FDOUT], STDOUT_FILENO) < 0) || | |
643 (dup2(errfds[FDOUT], STDERR_FILENO) < 0)) { | |
644 TRACE(("leave noptycommand: error redirecting FDs")) | |
645 return DROPBEAR_FAILURE; | |
646 } | |
647 | |
648 close(infds[FDOUT]); | |
649 close(infds[FDIN]); | |
650 close(outfds[FDIN]); | |
651 close(outfds[FDOUT]); | |
652 close(errfds[FDIN]); | |
653 close(errfds[FDOUT]); | |
654 | |
655 execchild(chansess); | |
656 /* not reached */ | |
657 | |
658 } else { | |
659 /* parent */ | |
660 TRACE(("continue noptycommand: parent")) | |
661 chansess->pid = pid; | |
662 | |
663 addchildpid(chansess, pid); | |
664 | |
665 if (svr_ses.lastexit.exitpid != -1) { | |
666 /* The child probably exited and the signal handler triggered | |
667 * possibly before we got around to adding the childpid. So we fill | |
668 * out it's data manually */ | |
669 for (i = 0; i < svr_ses.childpidsize; i++) { | |
670 if (svr_ses.childpids[i].pid == pid) { | |
671 svr_ses.childpids[i].chansess->exit = svr_ses.lastexit; | |
672 svr_ses.lastexit.exitpid = -1; | |
673 } | |
674 } | |
675 } | |
676 | |
677 | |
678 close(infds[FDIN]); | |
679 close(outfds[FDOUT]); | |
680 close(errfds[FDOUT]); | |
681 channel->writefd = infds[FDOUT]; | |
682 channel->readfd = outfds[FDIN]; | |
683 channel->errfd = errfds[FDIN]; | |
684 ses.maxfd = MAX(ses.maxfd, channel->writefd); | |
685 ses.maxfd = MAX(ses.maxfd, channel->readfd); | |
686 ses.maxfd = MAX(ses.maxfd, channel->errfd); | |
687 | |
688 setnonblocking(channel->readfd); | |
689 setnonblocking(channel->writefd); | |
690 setnonblocking(channel->errfd); | |
691 | |
692 } | |
693 #undef FDIN | |
694 #undef FDOUT | |
695 | |
696 TRACE(("leave noptycommand")) | |
697 return DROPBEAR_SUCCESS; | |
698 } | |
699 | |
700 /* Execute a command or shell within a pty environment, and set up | |
701 * redirection as appropriate. | |
702 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ | |
703 static int ptycommand(struct Channel *channel, struct ChanSess *chansess) { | |
704 | |
705 pid_t pid; | |
706 struct logininfo *li = NULL; | |
707 #ifdef DO_MOTD | |
708 buffer * motdbuf = NULL; | |
709 int len; | |
710 struct stat sb; | |
711 char *hushpath = NULL; | |
712 #endif | |
713 | |
714 TRACE(("enter ptycommand")) | |
715 | |
716 /* we need to have a pty allocated */ | |
717 if (chansess->master == -1 || chansess->tty == NULL) { | |
718 dropbear_log(LOG_WARNING, "no pty was allocated, couldn't execute"); | |
719 return DROPBEAR_FAILURE; | |
720 } | |
721 | |
722 #ifdef __uClinux__ | |
723 pid = vfork(); | |
724 #else | |
725 pid = fork(); | |
726 #endif | |
727 if (pid < 0) | |
728 return DROPBEAR_FAILURE; | |
729 | |
730 if (pid == 0) { | |
731 /* child */ | |
732 | |
733 /* redirect stdin/stdout/stderr */ | |
734 close(chansess->master); | |
735 | |
736 pty_make_controlling_tty(&chansess->slave, chansess->tty); | |
737 | |
738 if ((dup2(chansess->slave, STDIN_FILENO) < 0) || | |
739 (dup2(chansess->slave, STDERR_FILENO) < 0) || | |
740 (dup2(chansess->slave, STDOUT_FILENO) < 0)) { | |
741 TRACE(("leave ptycommand: error redirecting filedesc")) | |
742 return DROPBEAR_FAILURE; | |
743 } | |
744 | |
745 close(chansess->slave); | |
746 | |
747 /* write the utmp/wtmp login record - must be after changing the | |
748 * terminal used for stdout with the dup2 above */ | |
749 li= login_alloc_entry(getpid(), ses.authstate.username, | |
750 ses.remotehost, chansess->tty); | |
751 login_login(li); | |
752 login_free_entry(li); | |
753 | |
754 m_free(chansess->tty); | |
755 | |
756 #ifdef DO_MOTD | |
757 if (svr_opts.domotd) { | |
758 /* don't show the motd if ~/.hushlogin exists */ | |
759 | |
760 /* 11 == strlen("/hushlogin\0") */ | |
761 len = strlen(ses.authstate.pw->pw_dir) + 11; | |
762 | |
763 hushpath = m_malloc(len); | |
764 snprintf(hushpath, len, "%s/hushlogin", ses.authstate.pw->pw_dir); | |
765 | |
766 if (stat(hushpath, &sb) < 0) { | |
767 /* more than a screenful is stupid IMHO */ | |
768 motdbuf = buf_new(80 * 25); | |
769 if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) { | |
770 buf_setpos(motdbuf, 0); | |
771 while (motdbuf->pos != motdbuf->len) { | |
772 len = motdbuf->len - motdbuf->pos; | |
773 len = write(STDOUT_FILENO, | |
774 buf_getptr(motdbuf, len), len); | |
775 buf_incrpos(motdbuf, len); | |
776 } | |
777 } | |
778 buf_free(motdbuf); | |
779 } | |
780 m_free(hushpath); | |
781 } | |
782 #endif /* DO_MOTD */ | |
783 | |
784 execchild(chansess); | |
785 /* not reached */ | |
786 | |
787 } else { | |
788 /* parent */ | |
789 TRACE(("continue ptycommand: parent")) | |
790 chansess->pid = pid; | |
791 | |
792 /* add a child pid */ | |
793 addchildpid(chansess, pid); | |
794 | |
795 close(chansess->slave); | |
796 channel->writefd = chansess->master; | |
797 channel->readfd = chansess->master; | |
798 /* don't need to set stderr here */ | |
799 ses.maxfd = MAX(ses.maxfd, chansess->master); | |
800 | |
801 setnonblocking(chansess->master); | |
802 | |
803 } | |
804 | |
805 TRACE(("leave ptycommand")) | |
806 return DROPBEAR_SUCCESS; | |
807 } | |
808 | |
809 /* Add the pid of a child to the list for exit-handling */ | |
810 static void addchildpid(struct ChanSess *chansess, pid_t pid) { | |
811 | |
812 unsigned int i; | |
813 for (i = 0; i < svr_ses.childpidsize; i++) { | |
814 if (svr_ses.childpids[i].pid == -1) { | |
815 break; | |
816 } | |
817 } | |
818 | |
819 /* need to increase size */ | |
820 if (i == svr_ses.childpidsize) { | |
821 svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids, | |
822 sizeof(struct ChildPid) * (svr_ses.childpidsize+1)); | |
823 svr_ses.childpidsize++; | |
824 } | |
825 | |
826 svr_ses.childpids[i].pid = pid; | |
827 svr_ses.childpids[i].chansess = chansess; | |
828 | |
829 } | |
830 | |
831 /* Clean up, drop to user privileges, set up the environment and execute | |
832 * the command/shell. This function does not return. */ | |
833 static void execchild(struct ChanSess *chansess) { | |
834 | |
835 char *argv[4]; | |
836 char * usershell = NULL; | |
837 char * baseshell = NULL; | |
838 unsigned int i; | |
839 | |
840 /* with uClinux we'll have vfork()ed, so don't want to overwrite the | |
841 * hostkey. can't think of a workaround to clear it */ | |
842 #ifndef __uClinux__ | |
843 /* wipe the hostkey */ | |
844 sign_key_free(svr_opts.hostkey); | |
845 svr_opts.hostkey = NULL; | |
846 | |
847 /* overwrite the prng state */ | |
848 reseedrandom(); | |
849 #endif | |
850 | |
851 /* close file descriptors except stdin/stdout/stderr | |
852 * Need to be sure FDs are closed here to avoid reading files as root */ | |
853 for (i = 3; i <= (unsigned int)ses.maxfd; i++) { | |
854 m_close(i); | |
855 } | |
856 | |
857 /* clear environment */ | |
858 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD | |
859 * etc. This is hazardous, so should only be used for debugging. */ | |
860 #ifndef DEBUG_VALGRIND | |
861 #ifdef HAVE_CLEARENV | |
862 clearenv(); | |
863 #else /* don't HAVE_CLEARENV */ | |
864 /* Yay for posix. */ | |
865 if (environ) { | |
866 environ[0] = NULL; | |
867 } | |
868 #endif /* HAVE_CLEARENV */ | |
869 #endif /* DEBUG_VALGRIND */ | |
870 | |
871 /* We can only change uid/gid as root ... */ | |
872 if (getuid() == 0) { | |
873 | |
874 if ((setgid(ses.authstate.pw->pw_gid) < 0) || | |
875 (initgroups(ses.authstate.pw->pw_name, | |
876 ses.authstate.pw->pw_gid) < 0)) { | |
877 dropbear_exit("error changing user group"); | |
878 } | |
879 if (setuid(ses.authstate.pw->pw_uid) < 0) { | |
880 dropbear_exit("error changing user"); | |
881 } | |
882 } else { | |
883 /* ... but if the daemon is the same uid as the requested uid, we don't | |
884 * need to */ | |
885 | |
886 /* XXX - there is a minor issue here, in that if there are multiple | |
887 * usernames with the same uid, but differing groups, then the | |
888 * differing groups won't be set (as with initgroups()). The solution | |
889 * is for the sysadmin not to give out the UID twice */ | |
890 if (getuid() != ses.authstate.pw->pw_uid) { | |
891 dropbear_exit("couldn't change user as non-root"); | |
892 } | |
893 } | |
894 | |
895 /* an empty shell should be interpreted as "/bin/sh" */ | |
896 if (ses.authstate.pw->pw_shell[0] == '\0') { | |
897 usershell = "/bin/sh"; | |
898 } else { | |
899 usershell = ses.authstate.pw->pw_shell; | |
900 } | |
901 | |
902 /* set env vars */ | |
903 addnewvar("USER", ses.authstate.pw->pw_name); | |
904 addnewvar("LOGNAME", ses.authstate.pw->pw_name); | |
905 addnewvar("HOME", ses.authstate.pw->pw_dir); | |
906 addnewvar("SHELL", usershell); | |
907 if (chansess->term != NULL) { | |
908 addnewvar("TERM", chansess->term); | |
909 } | |
910 | |
911 /* change directory */ | |
912 if (chdir(ses.authstate.pw->pw_dir) < 0) { | |
913 dropbear_exit("error changing directory"); | |
914 } | |
915 | |
916 #ifndef DISABLE_X11FWD | |
917 /* set up X11 forwarding if enabled */ | |
918 x11setauth(chansess); | |
919 #endif | |
920 #ifndef DISABLE_AGENTFWD | |
921 /* set up agent env variable */ | |
922 agentset(chansess); | |
923 #endif | |
924 | |
925 /* Re-enable SIGPIPE for the executed process */ | |
926 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { | |
927 dropbear_exit("signal() error"); | |
928 } | |
929 | |
930 baseshell = basename(usershell); | |
931 | |
932 if (chansess->cmd != NULL) { | |
933 argv[0] = baseshell; | |
934 } else { | |
935 /* a login shell should be "-bash" for "/bin/bash" etc */ | |
936 int len = strlen(baseshell) + 2; /* 2 for "-" */ | |
937 argv[0] = (char*)m_malloc(len); | |
938 snprintf(argv[0], len, "-%s", baseshell); | |
939 } | |
940 | |
941 if (chansess->cmd != NULL) { | |
942 argv[1] = "-c"; | |
943 argv[2] = chansess->cmd; | |
944 argv[3] = NULL; | |
945 } else { | |
946 /* construct a shell of the form "-bash" etc */ | |
947 argv[1] = NULL; | |
948 } | |
949 | |
950 execv(usershell, argv); | |
951 | |
952 /* only reached on error */ | |
953 dropbear_exit("child failed"); | |
954 } | |
955 | |
956 const struct ChanType svrchansess = { | |
957 0, /* sepfds */ | |
958 "session", /* name */ | |
959 newchansess, /* inithandler */ | |
960 sesscheckclose, /* checkclosehandler */ | |
961 chansessionrequest, /* reqhandler */ | |
962 closechansess, /* closehandler */ | |
963 }; | |
964 | |
965 | |
966 /* Set up the general chansession environment, in particular child-exit | |
967 * handling */ | |
968 void svr_chansessinitialise() { | |
969 | |
970 struct sigaction sa_chld; | |
971 | |
972 /* single child process intially */ | |
973 svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid)); | |
974 svr_ses.childpids[0].pid = -1; /* unused */ | |
975 svr_ses.childpids[0].chansess = NULL; | |
976 svr_ses.childpidsize = 1; | |
977 svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */ | |
978 sa_chld.sa_handler = sesssigchild_handler; | |
979 sa_chld.sa_flags = SA_NOCLDSTOP; | |
980 if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { | |
981 dropbear_exit("signal() error"); | |
982 } | |
983 | |
984 } | |
985 | |
986 /* add a new environment variable, allocating space for the entry */ | |
987 void addnewvar(const char* param, const char* var) { | |
988 | |
989 char* newvar = NULL; | |
990 int plen, vlen; | |
991 | |
992 plen = strlen(param); | |
993 vlen = strlen(var); | |
994 | |
995 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */ | |
996 memcpy(newvar, param, plen); | |
997 newvar[plen] = '='; | |
998 memcpy(&newvar[plen+1], var, vlen); | |
999 newvar[plen+vlen+1] = '\0'; | |
1000 if (putenv(newvar) < 0) { | |
1001 dropbear_exit("environ error"); | |
1002 } | |
1003 } |