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"
41 /* Handles sessions (either shells or programs) requested by the client */
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(void *user_data_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 static int sesscheckclose(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
);
65 const struct ChanType svrchansess
= {
68 newchansess
, /* inithandler */
69 sesscheckclose
, /* checkclosehandler */
70 chansessionrequest
, /* reqhandler */
71 closechansess
, /* closehandler */
74 /* required to clear environment */
75 extern char** environ
;
77 static int sesscheckclose(struct Channel
*channel
) {
78 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
79 TRACE(("sesscheckclose, pid is %d", chansess
->exit
.exitpid
))
80 return chansess
->exit
.exitpid
!= -1;
83 /* Handler for childs exiting, store the state for return to the client */
85 /* There's a particular race we have to watch out for: if the forked child
86 * executes, exits, and this signal-handler is called, all before the parent
87 * gets to run, then the childpids[] array won't have the pid in it. Hence we
88 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
89 * the parent when it runs. This work correctly at least in the case of a
90 * single shell spawned (ie the usual case) */
91 static void sesssigchild_handler(int UNUSED(dummy
)) {
96 struct sigaction sa_chld
;
97 struct exitinfo
*exit
= NULL
;
99 const int saved_errno
= errno
;
101 /* Make channel handling code look for closed channels */
102 ses
.channel_signal_pending
= 1;
104 TRACE(("enter sigchld handler"))
105 while ((pid
= waitpid(-1, &status
, WNOHANG
)) > 0) {
106 TRACE(("sigchld handler: pid %d", pid
))
109 /* find the corresponding chansess */
110 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
111 if (svr_ses
.childpids
[i
].pid
== pid
) {
112 TRACE(("found match session"));
113 exit
= &svr_ses
.childpids
[i
].chansess
->exit
;
118 /* If the pid wasn't matched, then we might have hit the race mentioned
119 * above. So we just store the info for the parent to deal with */
121 TRACE(("using lastexit"));
122 exit
= &svr_ses
.lastexit
;
126 if (WIFEXITED(status
)) {
127 exit
->exitstatus
= WEXITSTATUS(status
);
129 if (WIFSIGNALED(status
)) {
130 exit
->exitsignal
= WTERMSIG(status
);
131 #if !defined(AIX) && defined(WCOREDUMP)
132 exit
->exitcore
= WCOREDUMP(status
);
137 /* we use this to determine how pid exited */
138 exit
->exitsignal
= -1;
141 /* Make sure that the main select() loop wakes up */
143 /* isserver is just a random byte to write. We can't do anything
144 about an error so should just ignore it */
145 if (write(ses
.signal_pipe
[1], &ses
.isserver
, 1) == 1
152 sa_chld
.sa_handler
= sesssigchild_handler
;
153 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
154 sigemptyset(&sa_chld
.sa_mask
);
155 sigaction(SIGCHLD
, &sa_chld
, NULL
);
156 TRACE(("leave sigchld handler"))
161 /* send the exit status or the signal causing termination for a session */
162 static void send_exitsignalstatus(struct Channel
*channel
) {
164 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
166 if (chansess
->exit
.exitpid
>= 0) {
167 if (chansess
->exit
.exitsignal
> 0) {
168 send_msg_chansess_exitsignal(channel
, chansess
);
170 send_msg_chansess_exitstatus(channel
, chansess
);
175 /* send the exitstatus to the client */
176 static void send_msg_chansess_exitstatus(struct Channel
* channel
,
177 struct ChanSess
* chansess
) {
179 dropbear_assert(chansess
->exit
.exitpid
!= -1);
180 dropbear_assert(chansess
->exit
.exitsignal
== -1);
184 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
185 buf_putint(ses
.writepayload
, channel
->remotechan
);
186 buf_putstring(ses
.writepayload
, "exit-status", 11);
187 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
188 buf_putint(ses
.writepayload
, chansess
->exit
.exitstatus
);
194 /* send the signal causing the exit to the client */
195 static void send_msg_chansess_exitsignal(struct Channel
* channel
,
196 struct ChanSess
* chansess
) {
199 char* signame
= NULL
;
200 dropbear_assert(chansess
->exit
.exitpid
!= -1);
201 dropbear_assert(chansess
->exit
.exitsignal
> 0);
203 TRACE(("send_msg_chansess_exitsignal %d", chansess
->exit
.exitsignal
))
207 /* we check that we can match a signal name, otherwise
208 * don't send anything */
209 for (i
= 0; signames
[i
].name
!= NULL
; i
++) {
210 if (signames
[i
].signal
== chansess
->exit
.exitsignal
) {
211 signame
= signames
[i
].name
;
216 if (signame
== NULL
) {
220 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
221 buf_putint(ses
.writepayload
, channel
->remotechan
);
222 buf_putstring(ses
.writepayload
, "exit-signal", 11);
223 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
224 buf_putstring(ses
.writepayload
, signame
, strlen(signame
));
225 buf_putbyte(ses
.writepayload
, chansess
->exit
.exitcore
);
226 buf_putstring(ses
.writepayload
, "", 0); /* error msg */
227 buf_putstring(ses
.writepayload
, "", 0); /* lang */
232 /* set up a session channel */
233 static int newchansess(struct Channel
*channel
) {
235 struct ChanSess
*chansess
;
237 TRACE(("new chansess %p", (void*)channel
))
239 dropbear_assert(channel
->typedata
== NULL
);
241 chansess
= (struct ChanSess
*)m_malloc(sizeof(struct ChanSess
));
242 chansess
->cmd
= NULL
;
243 chansess
->connection_string
= NULL
;
244 chansess
->client_string
= NULL
;
248 chansess
->master
= -1;
249 chansess
->slave
= -1;
250 chansess
->tty
= NULL
;
251 chansess
->term
= NULL
;
253 chansess
->exit
.exitpid
= -1;
255 channel
->typedata
= chansess
;
257 #ifndef DISABLE_X11FWD
258 chansess
->x11listener
= NULL
;
259 chansess
->x11authprot
= NULL
;
260 chansess
->x11authcookie
= NULL
;
263 #ifdef ENABLE_SVR_AGENTFWD
264 chansess
->agentlistener
= NULL
;
265 chansess
->agentfile
= NULL
;
266 chansess
->agentdir
= NULL
;
269 channel
->prio
= DROPBEAR_CHANNEL_PRIO_INTERACTIVE
;
275 static struct logininfo
*
276 chansess_login_alloc(struct ChanSess
*chansess
) {
277 struct logininfo
* li
;
278 li
= login_alloc_entry(chansess
->pid
, ses
.authstate
.username
,
279 svr_ses
.remotehost
, chansess
->tty
);
283 /* clean a session channel */
284 static void closechansess(struct Channel
*channel
) {
286 struct ChanSess
*chansess
;
288 struct logininfo
*li
;
290 TRACE(("enter closechansess"))
292 chansess
= (struct ChanSess
*)channel
->typedata
;
294 if (chansess
== NULL
) {
295 TRACE(("leave closechansess: chansess == NULL"))
299 send_exitsignalstatus(channel
);
301 m_free(chansess
->cmd
);
302 m_free(chansess
->term
);
304 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
305 m_free(chansess
->original_command
);
309 /* write the utmp/wtmp login record */
310 li
= chansess_login_alloc(chansess
);
312 login_free_entry(li
);
314 pty_release(chansess
->tty
);
315 m_free(chansess
->tty
);
318 #ifndef DISABLE_X11FWD
319 x11cleanup(chansess
);
322 #ifdef ENABLE_SVR_AGENTFWD
323 svr_agentcleanup(chansess
);
326 /* clear child pid entries */
327 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
328 if (svr_ses
.childpids
[i
].chansess
== chansess
) {
329 dropbear_assert(svr_ses
.childpids
[i
].pid
> 0);
330 TRACE(("closing pid %d", svr_ses
.childpids
[i
].pid
))
331 TRACE(("exitpid is %d", chansess
->exit
.exitpid
))
332 svr_ses
.childpids
[i
].pid
= -1;
333 svr_ses
.childpids
[i
].chansess
= NULL
;
339 TRACE(("leave closechansess"))
342 /* Handle requests for a channel. These can be execution requests,
343 * or x11/authagent forwarding. These are passed to appropriate handlers */
344 static void chansessionrequest(struct Channel
*channel
) {
347 unsigned int typelen
;
348 unsigned char wantreply
;
350 struct ChanSess
*chansess
;
352 TRACE(("enter chansessionrequest"))
354 type
= buf_getstring(ses
.payload
, &typelen
);
355 wantreply
= buf_getbool(ses
.payload
);
357 if (typelen
> MAX_NAME_LEN
) {
358 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
362 chansess
= (struct ChanSess
*)channel
->typedata
;
363 dropbear_assert(chansess
!= NULL
);
364 TRACE(("type is %s", type
))
366 if (strcmp(type
, "window-change") == 0) {
367 ret
= sessionwinchange(chansess
);
368 } else if (strcmp(type
, "shell") == 0) {
369 ret
= sessioncommand(channel
, chansess
, 0, 0);
370 } else if (strcmp(type
, "pty-req") == 0) {
371 ret
= sessionpty(chansess
);
372 } else if (strcmp(type
, "exec") == 0) {
373 ret
= sessioncommand(channel
, chansess
, 1, 0);
374 } else if (strcmp(type
, "subsystem") == 0) {
375 ret
= sessioncommand(channel
, chansess
, 1, 1);
376 #ifndef DISABLE_X11FWD
377 } else if (strcmp(type
, "x11-req") == 0) {
378 ret
= x11req(chansess
);
380 #ifdef ENABLE_SVR_AGENTFWD
381 } else if (strcmp(type
, "auth-agent-req@openssh.com") == 0) {
382 ret
= svr_agentreq(chansess
);
384 } else if (strcmp(type
, "signal") == 0) {
385 ret
= sessionsignal(chansess
);
387 /* etc, todo "env", "subsystem" */
393 if (ret
== DROPBEAR_SUCCESS
) {
394 send_msg_channel_success(channel
);
396 send_msg_channel_failure(channel
);
401 TRACE(("leave chansessionrequest"))
405 /* Send a signal to a session's process as requested by the client*/
406 static int sessionsignal(struct ChanSess
*chansess
) {
409 char* signame
= NULL
;
412 if (chansess
->pid
== 0) {
413 /* haven't got a process pid yet */
414 return DROPBEAR_FAILURE
;
417 signame
= buf_getstring(ses
.payload
, NULL
);
420 while (signames
[i
].name
!= 0) {
421 if (strcmp(signames
[i
].name
, signame
) == 0) {
422 sig
= signames
[i
].signal
;
432 return DROPBEAR_FAILURE
;
435 if (kill(chansess
->pid
, sig
) < 0) {
436 return DROPBEAR_FAILURE
;
439 return DROPBEAR_SUCCESS
;
442 /* Let the process know that the window size has changed, as notified from the
443 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
444 static int sessionwinchange(struct ChanSess
*chansess
) {
446 int termc
, termr
, termw
, termh
;
448 if (chansess
->master
< 0) {
449 /* haven't got a pty yet */
450 return DROPBEAR_FAILURE
;
453 termc
= buf_getint(ses
.payload
);
454 termr
= buf_getint(ses
.payload
);
455 termw
= buf_getint(ses
.payload
);
456 termh
= buf_getint(ses
.payload
);
458 pty_change_window_size(chansess
->master
, termr
, termc
, termw
, termh
);
460 return DROPBEAR_SUCCESS
;
463 static void get_termmodes(struct ChanSess
*chansess
) {
465 struct termios termio
;
466 unsigned char opcode
;
468 const struct TermCode
* termcode
;
471 TRACE(("enter get_termmodes"))
474 /* We'll ignore errors and continue if we can't set modes.
475 * We're ignoring baud rates since they seem evil */
476 if (tcgetattr(chansess
->master
, &termio
) == -1) {
480 len
= buf_getint(ses
.payload
);
481 TRACE(("term mode str %d p->l %d p->p %d",
482 len
, ses
.payload
->len
, ses
.payload
->pos
));
483 if (len
!= ses
.payload
->len
- ses
.payload
->pos
) {
484 dropbear_exit("Bad term mode string");
488 TRACE(("leave get_termmodes: empty terminal modes string"))
492 while (((opcode
= buf_getbyte(ses
.payload
)) != 0x00) && opcode
<= 159) {
494 /* must be before checking type, so that value is consumed even if
496 value
= buf_getint(ses
.payload
);
498 /* handle types of code */
499 if (opcode
> MAX_TERMCODE
) {
502 termcode
= &termcodes
[(unsigned int)opcode
];
505 switch (termcode
->type
) {
510 case TERMCODE_CONTROLCHAR
:
511 termio
.c_cc
[termcode
->mapcode
] = value
;
516 termio
.c_iflag
|= termcode
->mapcode
;
518 termio
.c_iflag
&= ~(termcode
->mapcode
);
522 case TERMCODE_OUTPUT
:
524 termio
.c_oflag
|= termcode
->mapcode
;
526 termio
.c_oflag
&= ~(termcode
->mapcode
);
532 termio
.c_lflag
|= termcode
->mapcode
;
534 termio
.c_lflag
&= ~(termcode
->mapcode
);
538 case TERMCODE_CONTROL
:
540 termio
.c_cflag
|= termcode
->mapcode
;
542 termio
.c_cflag
&= ~(termcode
->mapcode
);
548 if (tcsetattr(chansess
->master
, TCSANOW
, &termio
) < 0) {
549 dropbear_log(LOG_INFO
, "Error setting terminal attributes");
551 TRACE(("leave get_termmodes"))
554 /* Set up a session pty which will be used to execute the shell or program.
555 * The pty is allocated now, and kept for when the shell/program executes.
556 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
557 static int sessionpty(struct ChanSess
* chansess
) {
559 unsigned int termlen
;
561 struct passwd
* pw
= NULL
;
563 TRACE(("enter sessionpty"))
565 if (!svr_pubkey_allows_pty()) {
566 TRACE(("leave sessionpty : pty forbidden by public key option"))
567 return DROPBEAR_FAILURE
;
570 chansess
->term
= buf_getstring(ses
.payload
, &termlen
);
571 if (termlen
> MAX_TERM_LEN
) {
572 /* TODO send disconnect ? */
573 TRACE(("leave sessionpty: term len too long"))
574 return DROPBEAR_FAILURE
;
577 /* allocate the pty */
578 if (chansess
->master
!= -1) {
579 dropbear_exit("Multiple pty requests");
581 if (pty_allocate(&chansess
->master
, &chansess
->slave
, namebuf
, 64) == 0) {
582 TRACE(("leave sessionpty: failed to allocate pty"))
583 return DROPBEAR_FAILURE
;
586 chansess
->tty
= m_strdup(namebuf
);
587 if (!chansess
->tty
) {
588 dropbear_exit("Out of memory"); /* TODO disconnect */
591 pw
= getpwnam(ses
.authstate
.pw_name
);
593 dropbear_exit("getpwnam failed after succeeding previously");
594 pty_setowner(pw
, chansess
->tty
);
596 /* Set up the rows/col counts */
597 sessionwinchange(chansess
);
599 /* Read the terminal modes */
600 get_termmodes(chansess
);
602 TRACE(("leave sessionpty"))
603 return DROPBEAR_SUCCESS
;
607 static void make_connection_string(struct ChanSess
*chansess
) {
608 char *local_ip
, *local_port
, *remote_ip
, *remote_port
;
610 get_socket_address(ses
.sock_in
, &local_ip
, &local_port
, &remote_ip
, &remote_port
, 0);
612 /* "remoteip remoteport localip localport" */
613 len
= strlen(local_ip
) + strlen(remote_ip
) + 20;
614 chansess
->connection_string
= m_malloc(len
);
615 snprintf(chansess
->connection_string
, len
, "%s %s %s %s", remote_ip
, remote_port
, local_ip
, local_port
);
617 /* deprecated but bash only loads .bashrc if SSH_CLIENT is set */
618 /* "remoteip remoteport localport" */
619 len
= strlen(remote_ip
) + 20;
620 chansess
->client_string
= m_malloc(len
);
621 snprintf(chansess
->client_string
, len
, "%s %s %s", remote_ip
, remote_port
, local_port
);
630 /* Handle a command request from the client. This is used for both shell
631 * and command-execution requests, and passes the command to
632 * noptycommand or ptycommand as appropriate.
633 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
634 static int sessioncommand(struct Channel
*channel
, struct ChanSess
*chansess
,
635 int iscmd
, int issubsys
) {
640 TRACE(("enter sessioncommand"))
642 if (chansess
->cmd
!= NULL
) {
643 /* Note that only one command can _succeed_. The client might try
644 * one command (which fails), then try another. Ie fallback
645 * from sftp to scp */
646 return DROPBEAR_FAILURE
;
651 if (chansess
->cmd
== NULL
) {
652 chansess
->cmd
= buf_getstring(ses
.payload
, &cmdlen
);
654 if (cmdlen
> MAX_CMD_LEN
) {
655 m_free(chansess
->cmd
);
656 /* TODO - send error - too long ? */
657 return DROPBEAR_FAILURE
;
661 #ifdef SFTPSERVER_PATH
662 if ((cmdlen
== 4) && strncmp(chansess
->cmd
, "sftp", 4) == 0) {
663 m_free(chansess
->cmd
);
664 chansess
->cmd
= m_strdup(SFTPSERVER_PATH
);
668 m_free(chansess
->cmd
);
669 return DROPBEAR_FAILURE
;
674 /* take public key option 'command' into account */
675 svr_pubkey_set_forced_command(chansess
);
679 dropbear_log(LOG_INFO
, "User %s executing '%s'",
680 ses
.authstate
.pw_name
, chansess
->cmd
);
682 dropbear_log(LOG_INFO
, "User %s executing login shell",
683 ses
.authstate
.pw_name
);
687 /* uClinux will vfork(), so there'll be a race as
688 connection_string is freed below. */
690 make_connection_string(chansess
);
693 if (chansess
->term
== NULL
) {
695 ret
= noptycommand(channel
, chansess
);
696 if (ret
== DROPBEAR_SUCCESS
) {
697 channel
->prio
= DROPBEAR_CHANNEL_PRIO_BULK
;
698 update_channel_prio();
702 ret
= ptycommand(channel
, chansess
);
706 m_free(chansess
->connection_string
);
707 m_free(chansess
->client_string
);
710 if (ret
== DROPBEAR_FAILURE
) {
711 m_free(chansess
->cmd
);
716 /* Execute a command and set up redirection of stdin/stdout/stderr without a
718 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
719 static int noptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
722 TRACE(("enter noptycommand"))
723 ret
= spawn_command(execchild
, chansess
,
724 &channel
->writefd
, &channel
->readfd
, &channel
->errfd
,
727 if (ret
== DROPBEAR_FAILURE
) {
731 ses
.maxfd
= MAX(ses
.maxfd
, channel
->writefd
);
732 ses
.maxfd
= MAX(ses
.maxfd
, channel
->readfd
);
733 ses
.maxfd
= MAX(ses
.maxfd
, channel
->errfd
);
735 addchildpid(chansess
, chansess
->pid
);
737 if (svr_ses
.lastexit
.exitpid
!= -1) {
739 TRACE(("parent side: lastexitpid is %d", svr_ses
.lastexit
.exitpid
))
740 /* The child probably exited and the signal handler triggered
741 * possibly before we got around to adding the childpid. So we fill
742 * out its data manually */
743 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
744 if (svr_ses
.childpids
[i
].pid
== svr_ses
.lastexit
.exitpid
) {
745 TRACE(("found match for lastexitpid"))
746 svr_ses
.childpids
[i
].chansess
->exit
= svr_ses
.lastexit
;
747 svr_ses
.lastexit
.exitpid
= -1;
753 TRACE(("leave noptycommand"))
754 return DROPBEAR_SUCCESS
;
757 /* Execute a command or shell within a pty environment, and set up
758 * redirection as appropriate.
759 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
760 static int ptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
763 struct logininfo
*li
= NULL
;
765 buffer
* motdbuf
= NULL
;
768 char *hushpath
= NULL
;
771 TRACE(("enter ptycommand"))
773 /* we need to have a pty allocated */
774 if (chansess
->master
== -1 || chansess
->tty
== NULL
) {
775 dropbear_log(LOG_WARNING
, "No pty was allocated, couldn't execute");
776 return DROPBEAR_FAILURE
;
785 return DROPBEAR_FAILURE
;
790 TRACE(("back to normal sigchld"))
791 /* Revert to normal sigchld handling */
792 if (signal(SIGCHLD
, SIG_DFL
) == SIG_ERR
) {
793 dropbear_exit("signal() error");
796 /* redirect stdin/stdout/stderr */
797 close(chansess
->master
);
799 pty_make_controlling_tty(&chansess
->slave
, chansess
->tty
);
801 if ((dup2(chansess
->slave
, STDIN_FILENO
) < 0) ||
802 (dup2(chansess
->slave
, STDERR_FILENO
) < 0) ||
803 (dup2(chansess
->slave
, STDOUT_FILENO
) < 0)) {
804 TRACE(("leave ptycommand: error redirecting filedesc"))
805 return DROPBEAR_FAILURE
;
808 close(chansess
->slave
);
810 /* write the utmp/wtmp login record - must be after changing the
811 * terminal used for stdout with the dup2 above */
812 li
= chansess_login_alloc(chansess
);
814 login_free_entry(li
);
817 if (svr_opts
.domotd
&& !chansess
->cmd
) {
818 /* don't show the motd if ~/.hushlogin exists */
820 /* 12 == strlen("/.hushlogin\0") */
821 len
= strlen(ses
.authstate
.pw_dir
) + 12;
823 hushpath
= m_malloc(len
);
824 snprintf(hushpath
, len
, "%s/.hushlogin", ses
.authstate
.pw_dir
);
826 if (stat(hushpath
, &sb
) < 0) {
827 /* more than a screenful is stupid IMHO */
828 motdbuf
= buf_new(80 * 25);
829 if (buf_readfile(motdbuf
, MOTD_FILENAME
) == DROPBEAR_SUCCESS
) {
830 buf_setpos(motdbuf
, 0);
831 while (motdbuf
->pos
!= motdbuf
->len
) {
832 len
= motdbuf
->len
- motdbuf
->pos
;
833 len
= write(STDOUT_FILENO
,
834 buf_getptr(motdbuf
, len
), len
);
835 buf_incrpos(motdbuf
, len
);
849 TRACE(("continue ptycommand: parent"))
852 /* add a child pid */
853 addchildpid(chansess
, pid
);
855 close(chansess
->slave
);
856 channel
->writefd
= chansess
->master
;
857 channel
->readfd
= chansess
->master
;
858 /* don't need to set stderr here */
859 ses
.maxfd
= MAX(ses
.maxfd
, chansess
->master
);
861 setnonblocking(chansess
->master
);
865 TRACE(("leave ptycommand"))
866 return DROPBEAR_SUCCESS
;
869 /* Add the pid of a child to the list for exit-handling */
870 static void addchildpid(struct ChanSess
*chansess
, pid_t pid
) {
873 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
874 if (svr_ses
.childpids
[i
].pid
== -1) {
879 /* need to increase size */
880 if (i
== svr_ses
.childpidsize
) {
881 svr_ses
.childpids
= (struct ChildPid
*)m_realloc(svr_ses
.childpids
,
882 sizeof(struct ChildPid
) * (svr_ses
.childpidsize
+1));
883 svr_ses
.childpidsize
++;
886 svr_ses
.childpids
[i
].pid
= pid
;
887 svr_ses
.childpids
[i
].chansess
= chansess
;
891 /* Clean up, drop to user privileges, set up the environment and execute
892 * the command/shell. This function does not return. */
893 static void execchild(void *user_data
) {
894 struct ChanSess
*chansess
= user_data
;
895 char *usershell
= NULL
;
897 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
898 * hostkey. can't think of a workaround to clear it */
900 /* wipe the hostkey */
901 sign_key_free(svr_opts
.hostkey
);
902 svr_opts
.hostkey
= NULL
;
904 /* overwrite the prng state */
908 /* clear environment */
909 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
910 * etc. This is hazardous, so should only be used for debugging. */
911 #ifndef DEBUG_VALGRIND
914 #else /* don't HAVE_CLEARENV */
919 #endif /* HAVE_CLEARENV */
920 #endif /* DEBUG_VALGRIND */
922 /* We can only change uid/gid as root ... */
925 if ((setgid(ses
.authstate
.pw_gid
) < 0) ||
926 (initgroups(ses
.authstate
.pw_name
,
927 ses
.authstate
.pw_gid
) < 0)) {
928 dropbear_exit("Error changing user group");
930 if (setuid(ses
.authstate
.pw_uid
) < 0) {
931 dropbear_exit("Error changing user");
934 /* ... but if the daemon is the same uid as the requested uid, we don't
937 /* XXX - there is a minor issue here, in that if there are multiple
938 * usernames with the same uid, but differing groups, then the
939 * differing groups won't be set (as with initgroups()). The solution
940 * is for the sysadmin not to give out the UID twice */
941 if (getuid() != ses
.authstate
.pw_uid
) {
942 dropbear_exit("Couldn't change user as non-root");
947 addnewvar("USER", ses
.authstate
.pw_name
);
948 addnewvar("LOGNAME", ses
.authstate
.pw_name
);
949 addnewvar("HOME", ses
.authstate
.pw_dir
);
950 addnewvar("SHELL", get_user_shell());
951 addnewvar("PATH", DEFAULT_PATH
);
952 if (chansess
->term
!= NULL
) {
953 addnewvar("TERM", chansess
->term
);
957 addnewvar("SSH_TTY", chansess
->tty
);
960 if (chansess
->connection_string
) {
961 addnewvar("SSH_CONNECTION", chansess
->connection_string
);
964 if (chansess
->client_string
) {
965 addnewvar("SSH_CLIENT", chansess
->client_string
);
968 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
969 if (chansess
->original_command
) {
970 addnewvar("SSH_ORIGINAL_COMMAND", chansess
->original_command
);
974 /* change directory */
975 if (chdir(ses
.authstate
.pw_dir
) < 0) {
976 dropbear_exit("Error changing directory");
979 #ifndef DISABLE_X11FWD
980 /* set up X11 forwarding if enabled */
981 x11setauth(chansess
);
983 #ifdef ENABLE_SVR_AGENTFWD
984 /* set up agent env variable */
985 svr_agentset(chansess
);
988 usershell
= m_strdup(get_user_shell());
989 run_shell_command(chansess
->cmd
, ses
.maxfd
, usershell
);
991 /* only reached on error */
992 dropbear_exit("Child failed");
995 /* Set up the general chansession environment, in particular child-exit
997 void svr_chansessinitialise() {
999 struct sigaction sa_chld
;
1001 /* single child process intially */
1002 svr_ses
.childpids
= (struct ChildPid
*)m_malloc(sizeof(struct ChildPid
));
1003 svr_ses
.childpids
[0].pid
= -1; /* unused */
1004 svr_ses
.childpids
[0].chansess
= NULL
;
1005 svr_ses
.childpidsize
= 1;
1006 svr_ses
.lastexit
.exitpid
= -1; /* Nothing has exited yet */
1007 sa_chld
.sa_handler
= sesssigchild_handler
;
1008 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
1009 sigemptyset(&sa_chld
.sa_mask
);
1010 if (sigaction(SIGCHLD
, &sa_chld
, NULL
) < 0) {
1011 dropbear_exit("signal() error");
1016 /* add a new environment variable, allocating space for the entry */
1017 void addnewvar(const char* param
, const char* var
) {
1019 char* newvar
= NULL
;
1022 plen
= strlen(param
);
1025 newvar
= m_malloc(plen
+ vlen
+ 2); /* 2 is for '=' and '\0' */
1026 memcpy(newvar
, param
, plen
);
1028 memcpy(&newvar
[plen
+1], var
, vlen
);
1029 newvar
[plen
+vlen
+1] = '\0';
1030 /* newvar is leaked here, but that's part of putenv()'s semantics */
1031 if (putenv(newvar
) < 0) {
1032 dropbear_exit("environ error");