2 * Dropbear - a SSH2 server
4 * Copyright (c) 2002,2003 Matt Johnston
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:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
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
31 #include "chansession.h"
33 #include "termcodes.h"
42 /* Handles sessions (either shells or programs) requested by the client */
44 static int sessioncommand(struct Channel
*channel
, struct ChanSess
*chansess
,
45 int iscmd
, int issubsys
);
46 static int sessionpty(struct ChanSess
* chansess
);
47 static int sessionsignal(struct ChanSess
*chansess
);
48 static int noptycommand(struct Channel
*channel
, struct ChanSess
*chansess
);
49 static int ptycommand(struct Channel
*channel
, struct ChanSess
*chansess
);
50 static int sessionwinchange(struct ChanSess
*chansess
);
51 static void execchild(void *user_data_chansess
);
52 static void addchildpid(struct ChanSess
*chansess
, pid_t pid
);
53 static void sesssigchild_handler(int val
);
54 static void closechansess(struct Channel
*channel
);
55 static int newchansess(struct Channel
*channel
);
56 static void chansessionrequest(struct Channel
*channel
);
58 static void send_exitsignalstatus(struct Channel
*channel
);
59 static void send_msg_chansess_exitstatus(struct Channel
* channel
,
60 struct ChanSess
* chansess
);
61 static void send_msg_chansess_exitsignal(struct Channel
* channel
,
62 struct ChanSess
* chansess
);
63 static void get_termmodes(struct ChanSess
*chansess
);
66 /* required to clear environment */
67 extern char** environ
;
69 static int sesscheckclose(struct Channel
*channel
) {
70 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
71 TRACE(("sesscheckclose, pid is %d", chansess
->exit
.exitpid
))
72 return chansess
->exit
.exitpid
!= -1;
75 /* Handler for childs exiting, store the state for return to the client */
77 /* There's a particular race we have to watch out for: if the forked child
78 * executes, exits, and this signal-handler is called, all before the parent
79 * gets to run, then the childpids[] array won't have the pid in it. Hence we
80 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
81 * the parent when it runs. This work correctly at least in the case of a
82 * single shell spawned (ie the usual case) */
83 static void sesssigchild_handler(int UNUSED(dummy
)) {
88 struct sigaction sa_chld
;
89 struct exitinfo
*exit
= NULL
;
91 TRACE(("enter sigchld handler"))
92 while ((pid
= waitpid(-1, &status
, WNOHANG
)) > 0) {
93 TRACE(("sigchld handler: pid %d", pid
))
96 /* find the corresponding chansess */
97 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
98 if (svr_ses
.childpids
[i
].pid
== pid
) {
99 TRACE(("found match session"));
100 exit
= &svr_ses
.childpids
[i
].chansess
->exit
;
105 /* If the pid wasn't matched, then we might have hit the race mentioned
106 * above. So we just store the info for the parent to deal with */
108 TRACE(("using lastexit"));
109 exit
= &svr_ses
.lastexit
;
113 if (WIFEXITED(status
)) {
114 exit
->exitstatus
= WEXITSTATUS(status
);
116 if (WIFSIGNALED(status
)) {
117 exit
->exitsignal
= WTERMSIG(status
);
118 #if !defined(AIX) && defined(WCOREDUMP)
119 exit
->exitcore
= WCOREDUMP(status
);
124 /* we use this to determine how pid exited */
125 exit
->exitsignal
= -1;
128 /* Make sure that the main select() loop wakes up */
130 /* isserver is just a random byte to write. We can't do anything
131 about an error so should just ignore it */
132 if (write(ses
.signal_pipe
[1], &ses
.isserver
, 1) == 1
139 sa_chld
.sa_handler
= sesssigchild_handler
;
140 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
141 sigaction(SIGCHLD
, &sa_chld
, NULL
);
142 TRACE(("leave sigchld handler"))
145 /* send the exit status or the signal causing termination for a session */
146 static void send_exitsignalstatus(struct Channel
*channel
) {
148 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
150 if (chansess
->exit
.exitpid
>= 0) {
151 if (chansess
->exit
.exitsignal
> 0) {
152 send_msg_chansess_exitsignal(channel
, chansess
);
154 send_msg_chansess_exitstatus(channel
, chansess
);
159 /* send the exitstatus to the client */
160 static void send_msg_chansess_exitstatus(struct Channel
* channel
,
161 struct ChanSess
* chansess
) {
163 dropbear_assert(chansess
->exit
.exitpid
!= -1);
164 dropbear_assert(chansess
->exit
.exitsignal
== -1);
168 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
169 buf_putint(ses
.writepayload
, channel
->remotechan
);
170 buf_putstring(ses
.writepayload
, "exit-status", 11);
171 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
172 buf_putint(ses
.writepayload
, chansess
->exit
.exitstatus
);
178 /* send the signal causing the exit to the client */
179 static void send_msg_chansess_exitsignal(struct Channel
* channel
,
180 struct ChanSess
* chansess
) {
183 char* signame
= NULL
;
184 dropbear_assert(chansess
->exit
.exitpid
!= -1);
185 dropbear_assert(chansess
->exit
.exitsignal
> 0);
187 TRACE(("send_msg_chansess_exitsignal %d", chansess
->exit
.exitsignal
))
191 /* we check that we can match a signal name, otherwise
192 * don't send anything */
193 for (i
= 0; signames
[i
].name
!= NULL
; i
++) {
194 if (signames
[i
].signal
== chansess
->exit
.exitsignal
) {
195 signame
= signames
[i
].name
;
200 if (signame
== NULL
) {
204 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
205 buf_putint(ses
.writepayload
, channel
->remotechan
);
206 buf_putstring(ses
.writepayload
, "exit-signal", 11);
207 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
208 buf_putstring(ses
.writepayload
, signame
, strlen(signame
));
209 buf_putbyte(ses
.writepayload
, chansess
->exit
.exitcore
);
210 buf_putstring(ses
.writepayload
, "", 0); /* error msg */
211 buf_putstring(ses
.writepayload
, "", 0); /* lang */
216 /* set up a session channel */
217 static int newchansess(struct Channel
*channel
) {
219 struct ChanSess
*chansess
;
221 dropbear_assert(channel
->typedata
== NULL
);
223 chansess
= (struct ChanSess
*)m_malloc(sizeof(struct ChanSess
));
224 chansess
->cmd
= NULL
;
228 chansess
->master
= -1;
229 chansess
->slave
= -1;
230 chansess
->tty
= NULL
;
231 chansess
->term
= NULL
;
233 chansess
->exit
.exitpid
= -1;
235 channel
->typedata
= chansess
;
237 #ifndef DISABLE_X11FWD
238 chansess
->x11listener
= NULL
;
239 chansess
->x11authprot
= NULL
;
240 chansess
->x11authcookie
= NULL
;
243 #ifndef DISABLE_AGENTFWD
244 chansess
->agentlistener
= NULL
;
245 chansess
->agentfile
= NULL
;
246 chansess
->agentdir
= NULL
;
253 /* clean a session channel */
254 static void closechansess(struct Channel
*channel
) {
256 struct ChanSess
*chansess
;
258 struct logininfo
*li
;
260 TRACE(("enter closechansess"))
262 chansess
= (struct ChanSess
*)channel
->typedata
;
264 if (chansess
== NULL
) {
265 TRACE(("leave closechansess: chansess == NULL"))
269 send_exitsignalstatus(channel
);
271 m_free(chansess
->cmd
);
272 m_free(chansess
->term
);
275 /* write the utmp/wtmp login record */
276 li
= login_alloc_entry(chansess
->pid
, ses
.authstate
.username
,
277 ses
.remotehost
, chansess
->tty
);
279 login_free_entry(li
);
281 pty_release(chansess
->tty
);
282 m_free(chansess
->tty
);
285 #ifndef DISABLE_X11FWD
286 x11cleanup(chansess
);
289 #ifndef DISABLE_AGENTFWD
290 agentcleanup(chansess
);
293 /* clear child pid entries */
294 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
295 if (svr_ses
.childpids
[i
].chansess
== chansess
) {
296 dropbear_assert(svr_ses
.childpids
[i
].pid
> 0);
297 TRACE(("closing pid %d", svr_ses
.childpids
[i
].pid
))
298 TRACE(("exitpid is %d", chansess
->exit
.exitpid
))
299 svr_ses
.childpids
[i
].pid
= -1;
300 svr_ses
.childpids
[i
].chansess
= NULL
;
306 TRACE(("leave closechansess"))
309 /* Handle requests for a channel. These can be execution requests,
310 * or x11/authagent forwarding. These are passed to appropriate handlers */
311 static void chansessionrequest(struct Channel
*channel
) {
313 unsigned char * type
= NULL
;
314 unsigned int typelen
;
315 unsigned char wantreply
;
317 struct ChanSess
*chansess
;
319 TRACE(("enter chansessionrequest"))
321 type
= buf_getstring(ses
.payload
, &typelen
);
322 wantreply
= buf_getbool(ses
.payload
);
324 if (typelen
> MAX_NAME_LEN
) {
325 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
329 chansess
= (struct ChanSess
*)channel
->typedata
;
330 dropbear_assert(chansess
!= NULL
);
331 TRACE(("type is %s", type
))
333 if (strcmp(type
, "window-change") == 0) {
334 ret
= sessionwinchange(chansess
);
335 } else if (strcmp(type
, "shell") == 0) {
336 ret
= sessioncommand(channel
, chansess
, 0, 0);
337 } else if (strcmp(type
, "pty-req") == 0) {
338 ret
= sessionpty(chansess
);
339 } else if (strcmp(type
, "exec") == 0) {
340 ret
= sessioncommand(channel
, chansess
, 1, 0);
341 } else if (strcmp(type
, "subsystem") == 0) {
342 ret
= sessioncommand(channel
, chansess
, 1, 1);
343 #ifndef DISABLE_X11FWD
344 } else if (strcmp(type
, "x11-req") == 0) {
345 ret
= x11req(chansess
);
347 #ifndef DISABLE_AGENTFWD
348 } else if (strcmp(type
, "auth-agent-req@openssh.com") == 0) {
349 ret
= agentreq(chansess
);
351 } else if (strcmp(type
, "signal") == 0) {
352 ret
= sessionsignal(chansess
);
354 /* etc, todo "env", "subsystem" */
360 if (ret
== DROPBEAR_SUCCESS
) {
361 send_msg_channel_success(channel
);
363 send_msg_channel_failure(channel
);
368 TRACE(("leave chansessionrequest"))
372 /* Send a signal to a session's process as requested by the client*/
373 static int sessionsignal(struct ChanSess
*chansess
) {
376 unsigned char* signame
= NULL
;
379 if (chansess
->pid
== 0) {
380 /* haven't got a process pid yet */
381 return DROPBEAR_FAILURE
;
384 signame
= buf_getstring(ses
.payload
, NULL
);
387 while (signames
[i
].name
!= 0) {
388 if (strcmp(signames
[i
].name
, signame
) == 0) {
389 sig
= signames
[i
].signal
;
399 return DROPBEAR_FAILURE
;
402 if (kill(chansess
->pid
, sig
) < 0) {
403 return DROPBEAR_FAILURE
;
406 return DROPBEAR_SUCCESS
;
409 /* Let the process know that the window size has changed, as notified from the
410 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
411 static int sessionwinchange(struct ChanSess
*chansess
) {
413 int termc
, termr
, termw
, termh
;
415 if (chansess
->master
< 0) {
416 /* haven't got a pty yet */
417 return DROPBEAR_FAILURE
;
420 termc
= buf_getint(ses
.payload
);
421 termr
= buf_getint(ses
.payload
);
422 termw
= buf_getint(ses
.payload
);
423 termh
= buf_getint(ses
.payload
);
425 pty_change_window_size(chansess
->master
, termr
, termc
, termw
, termh
);
427 return DROPBEAR_SUCCESS
;
430 static void get_termmodes(struct ChanSess
*chansess
) {
432 struct termios termio
;
433 unsigned char opcode
;
435 const struct TermCode
* termcode
;
438 TRACE(("enter get_termmodes"))
441 /* We'll ignore errors and continue if we can't set modes.
442 * We're ignoring baud rates since they seem evil */
443 if (tcgetattr(chansess
->master
, &termio
) == -1) {
447 len
= buf_getint(ses
.payload
);
448 TRACE(("term mode str %d p->l %d p->p %d",
449 len
, ses
.payload
->len
, ses
.payload
->pos
));
450 if (len
!= ses
.payload
->len
- ses
.payload
->pos
) {
451 dropbear_exit("bad term mode string");
455 TRACE(("leave get_termmodes: empty terminal modes string"))
459 while (((opcode
= buf_getbyte(ses
.payload
)) != 0x00) && opcode
<= 159) {
461 /* must be before checking type, so that value is consumed even if
463 value
= buf_getint(ses
.payload
);
465 /* handle types of code */
466 if (opcode
> MAX_TERMCODE
) {
469 termcode
= &termcodes
[(unsigned int)opcode
];
472 switch (termcode
->type
) {
477 case TERMCODE_CONTROLCHAR
:
478 termio
.c_cc
[termcode
->mapcode
] = value
;
483 termio
.c_iflag
|= termcode
->mapcode
;
485 termio
.c_iflag
&= ~(termcode
->mapcode
);
489 case TERMCODE_OUTPUT
:
491 termio
.c_oflag
|= termcode
->mapcode
;
493 termio
.c_oflag
&= ~(termcode
->mapcode
);
499 termio
.c_lflag
|= termcode
->mapcode
;
501 termio
.c_lflag
&= ~(termcode
->mapcode
);
505 case TERMCODE_CONTROL
:
507 termio
.c_cflag
|= termcode
->mapcode
;
509 termio
.c_cflag
&= ~(termcode
->mapcode
);
515 if (tcsetattr(chansess
->master
, TCSANOW
, &termio
) < 0) {
516 dropbear_log(LOG_INFO
, "error setting terminal attributes");
518 TRACE(("leave get_termmodes"))
521 /* Set up a session pty which will be used to execute the shell or program.
522 * The pty is allocated now, and kept for when the shell/program executes.
523 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
524 static int sessionpty(struct ChanSess
* chansess
) {
526 unsigned int termlen
;
527 unsigned char namebuf
[65];
528 struct passwd
* pw
= NULL
;
530 TRACE(("enter sessionpty"))
532 if (!svr_pubkey_allows_pty()) {
533 TRACE(("leave sessionpty : pty forbidden by public key option"))
534 return DROPBEAR_FAILURE
;
537 chansess
->term
= buf_getstring(ses
.payload
, &termlen
);
538 if (termlen
> MAX_TERM_LEN
) {
539 /* TODO send disconnect ? */
540 TRACE(("leave sessionpty: term len too long"))
541 return DROPBEAR_FAILURE
;
544 /* allocate the pty */
545 if (chansess
->master
!= -1) {
546 dropbear_exit("multiple pty requests");
548 if (pty_allocate(&chansess
->master
, &chansess
->slave
, namebuf
, 64) == 0) {
549 TRACE(("leave sessionpty: failed to allocate pty"))
550 return DROPBEAR_FAILURE
;
553 chansess
->tty
= (char*)m_strdup(namebuf
);
554 if (!chansess
->tty
) {
555 dropbear_exit("out of memory"); /* TODO disconnect */
558 pw
= getpwnam(ses
.authstate
.pw_name
);
560 dropbear_exit("getpwnam failed after succeeding previously");
561 pty_setowner(pw
, chansess
->tty
);
563 /* Set up the rows/col counts */
564 sessionwinchange(chansess
);
566 /* Read the terminal modes */
567 get_termmodes(chansess
);
569 TRACE(("leave sessionpty"))
570 return DROPBEAR_SUCCESS
;
573 /* Handle a command request from the client. This is used for both shell
574 * and command-execution requests, and passes the command to
575 * noptycommand or ptycommand as appropriate.
576 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
577 static int sessioncommand(struct Channel
*channel
, struct ChanSess
*chansess
,
578 int iscmd
, int issubsys
) {
583 TRACE(("enter sessioncommand"))
585 if (chansess
->cmd
!= NULL
) {
586 /* Note that only one command can _succeed_. The client might try
587 * one command (which fails), then try another. Ie fallback
588 * from sftp to scp */
589 return DROPBEAR_FAILURE
;
592 /* take public key option 'command' into account */
593 svr_pubkey_set_forced_command(chansess
);
597 if (chansess
->cmd
== NULL
) {
598 chansess
->cmd
= buf_getstring(ses
.payload
, &cmdlen
);
600 if (cmdlen
> MAX_CMD_LEN
) {
601 m_free(chansess
->cmd
);
602 /* TODO - send error - too long ? */
603 return DROPBEAR_FAILURE
;
607 #ifdef SFTPSERVER_PATH
608 if ((cmdlen
== 4) && strncmp(chansess
->cmd
, "sftp", 4) == 0) {
609 m_free(chansess
->cmd
);
610 chansess
->cmd
= m_strdup(SFTPSERVER_PATH
);
614 m_free(chansess
->cmd
);
615 return DROPBEAR_FAILURE
;
622 dropbear_log(LOG_INFO
, "user %s executing '%s'",
623 ses
.authstate
.pw_name
, chansess
->cmd
);
625 dropbear_log(LOG_INFO
, "user %s executing login shell",
626 ses
.authstate
.pw_name
);
630 if (chansess
->term
== NULL
) {
632 ret
= noptycommand(channel
, chansess
);
635 ret
= ptycommand(channel
, chansess
);
638 if (ret
== DROPBEAR_FAILURE
) {
639 m_free(chansess
->cmd
);
644 /* Execute a command and set up redirection of stdin/stdout/stderr without a
646 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
647 static int noptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
650 TRACE(("enter noptycommand"))
651 ret
= spawn_command(execchild
, chansess
,
652 &channel
->writefd
, &channel
->readfd
, &channel
->errfd
,
655 if (ret
== DROPBEAR_FAILURE
) {
659 ses
.maxfd
= MAX(ses
.maxfd
, channel
->writefd
);
660 ses
.maxfd
= MAX(ses
.maxfd
, channel
->readfd
);
661 ses
.maxfd
= MAX(ses
.maxfd
, channel
->errfd
);
663 addchildpid(chansess
, chansess
->pid
);
665 if (svr_ses
.lastexit
.exitpid
!= -1) {
667 TRACE(("parent side: lastexitpid is %d", svr_ses
.lastexit
.exitpid
))
668 /* The child probably exited and the signal handler triggered
669 * possibly before we got around to adding the childpid. So we fill
670 * out its data manually */
671 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
672 if (svr_ses
.childpids
[i
].pid
== svr_ses
.lastexit
.exitpid
) {
673 TRACE(("found match for lastexitpid"))
674 svr_ses
.childpids
[i
].chansess
->exit
= svr_ses
.lastexit
;
675 svr_ses
.lastexit
.exitpid
= -1;
680 TRACE(("leave noptycommand"))
681 return DROPBEAR_SUCCESS
;
684 /* Execute a command or shell within a pty environment, and set up
685 * redirection as appropriate.
686 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
687 static int ptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
690 struct logininfo
*li
= NULL
;
692 buffer
* motdbuf
= NULL
;
695 char *hushpath
= NULL
;
698 TRACE(("enter ptycommand"))
700 /* we need to have a pty allocated */
701 if (chansess
->master
== -1 || chansess
->tty
== NULL
) {
702 dropbear_log(LOG_WARNING
, "no pty was allocated, couldn't execute");
703 return DROPBEAR_FAILURE
;
712 return DROPBEAR_FAILURE
;
717 TRACE(("back to normal sigchld"))
718 /* Revert to normal sigchld handling */
719 if (signal(SIGCHLD
, SIG_DFL
) == SIG_ERR
) {
720 dropbear_exit("signal() error");
723 /* redirect stdin/stdout/stderr */
724 close(chansess
->master
);
726 pty_make_controlling_tty(&chansess
->slave
, chansess
->tty
);
728 if ((dup2(chansess
->slave
, STDIN_FILENO
) < 0) ||
729 (dup2(chansess
->slave
, STDERR_FILENO
) < 0) ||
730 (dup2(chansess
->slave
, STDOUT_FILENO
) < 0)) {
731 TRACE(("leave ptycommand: error redirecting filedesc"))
732 return DROPBEAR_FAILURE
;
735 close(chansess
->slave
);
737 /* write the utmp/wtmp login record - must be after changing the
738 * terminal used for stdout with the dup2 above */
739 li
= login_alloc_entry(getpid(), ses
.authstate
.username
,
740 ses
.remotehost
, chansess
->tty
);
742 login_free_entry(li
);
744 m_free(chansess
->tty
);
747 if (svr_opts
.domotd
) {
748 /* don't show the motd if ~/.hushlogin exists */
750 /* 12 == strlen("/.hushlogin\0") */
751 len
= strlen(ses
.authstate
.pw_dir
) + 12;
753 hushpath
= m_malloc(len
);
754 snprintf(hushpath
, len
, "%s/.hushlogin", ses
.authstate
.pw_dir
);
756 if (stat(hushpath
, &sb
) < 0) {
757 /* more than a screenful is stupid IMHO */
758 motdbuf
= buf_new(80 * 25);
759 if (buf_readfile(motdbuf
, MOTD_FILENAME
) == DROPBEAR_SUCCESS
) {
760 buf_setpos(motdbuf
, 0);
761 while (motdbuf
->pos
!= motdbuf
->len
) {
762 len
= motdbuf
->len
- motdbuf
->pos
;
763 len
= write(STDOUT_FILENO
,
764 buf_getptr(motdbuf
, len
), len
);
765 buf_incrpos(motdbuf
, len
);
779 TRACE(("continue ptycommand: parent"))
782 /* add a child pid */
783 addchildpid(chansess
, pid
);
785 close(chansess
->slave
);
786 channel
->writefd
= chansess
->master
;
787 channel
->readfd
= chansess
->master
;
788 /* don't need to set stderr here */
789 ses
.maxfd
= MAX(ses
.maxfd
, chansess
->master
);
791 setnonblocking(chansess
->master
);
795 TRACE(("leave ptycommand"))
796 return DROPBEAR_SUCCESS
;
799 /* Add the pid of a child to the list for exit-handling */
800 static void addchildpid(struct ChanSess
*chansess
, pid_t pid
) {
803 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
804 if (svr_ses
.childpids
[i
].pid
== -1) {
809 /* need to increase size */
810 if (i
== svr_ses
.childpidsize
) {
811 svr_ses
.childpids
= (struct ChildPid
*)m_realloc(svr_ses
.childpids
,
812 sizeof(struct ChildPid
) * (svr_ses
.childpidsize
+1));
813 svr_ses
.childpidsize
++;
816 svr_ses
.childpids
[i
].pid
= pid
;
817 svr_ses
.childpids
[i
].chansess
= chansess
;
821 /* Clean up, drop to user privileges, set up the environment and execute
822 * the command/shell. This function does not return. */
823 static void execchild(void *user_data
) {
824 struct ChanSess
*chansess
= user_data
;
825 char *usershell
= NULL
;
827 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
828 * hostkey. can't think of a workaround to clear it */
830 /* wipe the hostkey */
831 sign_key_free(svr_opts
.hostkey
);
832 svr_opts
.hostkey
= NULL
;
834 /* overwrite the prng state */
838 /* clear environment */
839 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
840 * etc. This is hazardous, so should only be used for debugging. */
841 #ifndef DEBUG_VALGRIND
844 #else /* don't HAVE_CLEARENV */
849 #endif /* HAVE_CLEARENV */
850 #endif /* DEBUG_VALGRIND */
852 /* We can only change uid/gid as root ... */
855 if ((setgid(ses
.authstate
.pw_gid
) < 0) ||
856 (initgroups(ses
.authstate
.pw_name
,
857 ses
.authstate
.pw_gid
) < 0)) {
858 dropbear_exit("error changing user group");
860 if (setuid(ses
.authstate
.pw_uid
) < 0) {
861 dropbear_exit("error changing user");
864 /* ... but if the daemon is the same uid as the requested uid, we don't
867 /* XXX - there is a minor issue here, in that if there are multiple
868 * usernames with the same uid, but differing groups, then the
869 * differing groups won't be set (as with initgroups()). The solution
870 * is for the sysadmin not to give out the UID twice */
871 if (getuid() != ses
.authstate
.pw_uid
) {
872 dropbear_exit("couldn't change user as non-root");
877 addnewvar("USER", ses
.authstate
.pw_name
);
878 addnewvar("LOGNAME", ses
.authstate
.pw_name
);
879 addnewvar("HOME", ses
.authstate
.pw_dir
);
880 addnewvar("SHELL", get_user_shell());
881 addnewvar("PATH", DEFAULT_PATH
);
882 if (chansess
->term
!= NULL
) {
883 addnewvar("TERM", chansess
->term
);
886 /* change directory */
887 if (chdir(ses
.authstate
.pw_dir
) < 0) {
888 dropbear_exit("error changing directory");
891 #ifndef DISABLE_X11FWD
892 /* set up X11 forwarding if enabled */
893 x11setauth(chansess
);
895 #ifndef DISABLE_AGENTFWD
896 /* set up agent env variable */
900 usershell
= m_strdup(get_user_shell());
901 run_shell_command(chansess
->cmd
, ses
.maxfd
, usershell
);
903 /* only reached on error */
904 dropbear_exit("child failed");
907 const struct ChanType svrchansess
= {
909 "session", /* name */
910 newchansess
, /* inithandler */
911 sesscheckclose
, /* checkclosehandler */
912 chansessionrequest
, /* reqhandler */
913 closechansess
, /* closehandler */
917 /* Set up the general chansession environment, in particular child-exit
919 void svr_chansessinitialise() {
921 struct sigaction sa_chld
;
923 /* single child process intially */
924 svr_ses
.childpids
= (struct ChildPid
*)m_malloc(sizeof(struct ChildPid
));
925 svr_ses
.childpids
[0].pid
= -1; /* unused */
926 svr_ses
.childpids
[0].chansess
= NULL
;
927 svr_ses
.childpidsize
= 1;
928 svr_ses
.lastexit
.exitpid
= -1; /* Nothing has exited yet */
929 sa_chld
.sa_handler
= sesssigchild_handler
;
930 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
931 if (sigaction(SIGCHLD
, &sa_chld
, NULL
) < 0) {
932 dropbear_exit("signal() error");
937 /* add a new environment variable, allocating space for the entry */
938 void addnewvar(const char* param
, const char* var
) {
943 plen
= strlen(param
);
946 newvar
= m_malloc(plen
+ vlen
+ 2); /* 2 is for '=' and '\0' */
947 memcpy(newvar
, param
, plen
);
949 memcpy(&newvar
[plen
+1], var
, vlen
);
950 newvar
[plen
+vlen
+1] = '\0';
951 /* newvar is leaked here, but that's part of putenv()'s semantics */
952 if (putenv(newvar
) < 0) {
953 dropbear_exit("environ error");