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 }