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
);
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 void get_termmodes(struct ChanSess
*chansess
);
65 /* required to clear environment */
66 extern char** environ
;
68 static int sesscheckclose(struct Channel
*channel
) {
69 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
70 TRACE(("sesscheckclose, pid is %d", chansess
->exit
.exitpid
))
71 return chansess
->exit
.exitpid
!= -1;
74 /* Handler for childs exiting, store the state for return to the client */
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
)) {
87 struct sigaction sa_chld
;
88 struct exitinfo
*exit
= NULL
;
90 TRACE(("enter sigchld handler"))
91 while ((pid
= waitpid(-1, &status
, WNOHANG
)) > 0) {
92 TRACE(("sigchld handler: pid %d", pid
))
95 /* find the corresponding chansess */
96 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
97 if (svr_ses
.childpids
[i
].pid
== pid
) {
98 TRACE(("found match session"));
99 exit
= &svr_ses
.childpids
[i
].chansess
->exit
;
104 /* If the pid wasn't matched, then we might have hit the race mentioned
105 * above. So we just store the info for the parent to deal with */
107 TRACE(("using lastexit"));
108 exit
= &svr_ses
.lastexit
;
112 if (WIFEXITED(status
)) {
113 exit
->exitstatus
= WEXITSTATUS(status
);
115 if (WIFSIGNALED(status
)) {
116 exit
->exitsignal
= WTERMSIG(status
);
117 #if !defined(AIX) && defined(WCOREDUMP)
118 exit
->exitcore
= WCOREDUMP(status
);
123 /* we use this to determine how pid exited */
124 exit
->exitsignal
= -1;
127 /* Make sure that the main select() loop wakes up */
129 /* isserver is just a random byte to write. We can't do anything
130 about an error so should just ignore it */
131 if (write(ses
.signal_pipe
[1], &ses
.isserver
, 1) == 1
138 sa_chld
.sa_handler
= sesssigchild_handler
;
139 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
140 sigaction(SIGCHLD
, &sa_chld
, NULL
);
141 TRACE(("leave sigchld handler"))
144 /* send the exit status or the signal causing termination for a session */
145 static void send_exitsignalstatus(struct Channel
*channel
) {
147 struct ChanSess
*chansess
= (struct ChanSess
*)channel
->typedata
;
149 if (chansess
->exit
.exitpid
>= 0) {
150 if (chansess
->exit
.exitsignal
> 0) {
151 send_msg_chansess_exitsignal(channel
, chansess
);
153 send_msg_chansess_exitstatus(channel
, chansess
);
158 /* send the exitstatus to the client */
159 static void send_msg_chansess_exitstatus(struct Channel
* channel
,
160 struct ChanSess
* chansess
) {
162 dropbear_assert(chansess
->exit
.exitpid
!= -1);
163 dropbear_assert(chansess
->exit
.exitsignal
== -1);
167 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
168 buf_putint(ses
.writepayload
, channel
->remotechan
);
169 buf_putstring(ses
.writepayload
, "exit-status", 11);
170 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
171 buf_putint(ses
.writepayload
, chansess
->exit
.exitstatus
);
177 /* send the signal causing the exit to the client */
178 static void send_msg_chansess_exitsignal(struct Channel
* channel
,
179 struct ChanSess
* chansess
) {
182 char* signame
= NULL
;
183 dropbear_assert(chansess
->exit
.exitpid
!= -1);
184 dropbear_assert(chansess
->exit
.exitsignal
> 0);
186 TRACE(("send_msg_chansess_exitsignal %d", chansess
->exit
.exitsignal
))
190 /* we check that we can match a signal name, otherwise
191 * don't send anything */
192 for (i
= 0; signames
[i
].name
!= NULL
; i
++) {
193 if (signames
[i
].signal
== chansess
->exit
.exitsignal
) {
194 signame
= signames
[i
].name
;
199 if (signame
== NULL
) {
203 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
204 buf_putint(ses
.writepayload
, channel
->remotechan
);
205 buf_putstring(ses
.writepayload
, "exit-signal", 11);
206 buf_putbyte(ses
.writepayload
, 0); /* boolean FALSE */
207 buf_putstring(ses
.writepayload
, signame
, strlen(signame
));
208 buf_putbyte(ses
.writepayload
, chansess
->exit
.exitcore
);
209 buf_putstring(ses
.writepayload
, "", 0); /* error msg */
210 buf_putstring(ses
.writepayload
, "", 0); /* lang */
215 /* set up a session channel */
216 static int newchansess(struct Channel
*channel
) {
218 struct ChanSess
*chansess
;
220 dropbear_assert(channel
->typedata
== NULL
);
222 chansess
= (struct ChanSess
*)m_malloc(sizeof(struct ChanSess
));
223 chansess
->cmd
= NULL
;
224 chansess
->connection_string
= 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 static struct logininfo
*
254 chansess_login_alloc(struct ChanSess
*chansess
) {
255 struct logininfo
* li
;
256 li
= login_alloc_entry(chansess
->pid
, ses
.authstate
.username
,
257 svr_ses
.remotehost
, chansess
->tty
);
261 /* clean a session channel */
262 static void closechansess(struct Channel
*channel
) {
264 struct ChanSess
*chansess
;
266 struct logininfo
*li
;
268 TRACE(("enter closechansess"))
270 chansess
= (struct ChanSess
*)channel
->typedata
;
272 if (chansess
== NULL
) {
273 TRACE(("leave closechansess: chansess == NULL"))
277 send_exitsignalstatus(channel
);
279 m_free(chansess
->cmd
);
280 m_free(chansess
->term
);
283 /* write the utmp/wtmp login record */
284 li
= chansess_login_alloc(chansess
);
286 login_free_entry(li
);
288 pty_release(chansess
->tty
);
289 m_free(chansess
->tty
);
292 #ifndef DISABLE_X11FWD
293 x11cleanup(chansess
);
296 #ifndef DISABLE_AGENTFWD
297 svr_agentcleanup(chansess
);
300 /* clear child pid entries */
301 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
302 if (svr_ses
.childpids
[i
].chansess
== chansess
) {
303 dropbear_assert(svr_ses
.childpids
[i
].pid
> 0);
304 TRACE(("closing pid %d", svr_ses
.childpids
[i
].pid
))
305 TRACE(("exitpid is %d", chansess
->exit
.exitpid
))
306 svr_ses
.childpids
[i
].pid
= -1;
307 svr_ses
.childpids
[i
].chansess
= NULL
;
313 TRACE(("leave closechansess"))
316 /* Handle requests for a channel. These can be execution requests,
317 * or x11/authagent forwarding. These are passed to appropriate handlers */
318 static void chansessionrequest(struct Channel
*channel
) {
320 unsigned char * type
= NULL
;
321 unsigned int typelen
;
322 unsigned char wantreply
;
324 struct ChanSess
*chansess
;
326 TRACE(("enter chansessionrequest"))
328 type
= buf_getstring(ses
.payload
, &typelen
);
329 wantreply
= buf_getbool(ses
.payload
);
331 if (typelen
> MAX_NAME_LEN
) {
332 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
336 chansess
= (struct ChanSess
*)channel
->typedata
;
337 dropbear_assert(chansess
!= NULL
);
338 TRACE(("type is %s", type
))
340 if (strcmp(type
, "window-change") == 0) {
341 ret
= sessionwinchange(chansess
);
342 } else if (strcmp(type
, "shell") == 0) {
343 ret
= sessioncommand(channel
, chansess
, 0, 0);
344 } else if (strcmp(type
, "pty-req") == 0) {
345 ret
= sessionpty(chansess
);
346 } else if (strcmp(type
, "exec") == 0) {
347 ret
= sessioncommand(channel
, chansess
, 1, 0);
348 } else if (strcmp(type
, "subsystem") == 0) {
349 ret
= sessioncommand(channel
, chansess
, 1, 1);
350 #ifndef DISABLE_X11FWD
351 } else if (strcmp(type
, "x11-req") == 0) {
352 ret
= x11req(chansess
);
354 #ifndef DISABLE_AGENTFWD
355 } else if (strcmp(type
, "auth-agent-req@openssh.com") == 0) {
356 ret
= svr_agentreq(chansess
);
358 } else if (strcmp(type
, "signal") == 0) {
359 ret
= sessionsignal(chansess
);
361 /* etc, todo "env", "subsystem" */
367 if (ret
== DROPBEAR_SUCCESS
) {
368 send_msg_channel_success(channel
);
370 send_msg_channel_failure(channel
);
375 TRACE(("leave chansessionrequest"))
379 /* Send a signal to a session's process as requested by the client*/
380 static int sessionsignal(struct ChanSess
*chansess
) {
383 unsigned char* signame
= NULL
;
386 if (chansess
->pid
== 0) {
387 /* haven't got a process pid yet */
388 return DROPBEAR_FAILURE
;
391 signame
= buf_getstring(ses
.payload
, NULL
);
394 while (signames
[i
].name
!= 0) {
395 if (strcmp(signames
[i
].name
, signame
) == 0) {
396 sig
= signames
[i
].signal
;
406 return DROPBEAR_FAILURE
;
409 if (kill(chansess
->pid
, sig
) < 0) {
410 return DROPBEAR_FAILURE
;
413 return DROPBEAR_SUCCESS
;
416 /* Let the process know that the window size has changed, as notified from the
417 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
418 static int sessionwinchange(struct ChanSess
*chansess
) {
420 int termc
, termr
, termw
, termh
;
422 if (chansess
->master
< 0) {
423 /* haven't got a pty yet */
424 return DROPBEAR_FAILURE
;
427 termc
= buf_getint(ses
.payload
);
428 termr
= buf_getint(ses
.payload
);
429 termw
= buf_getint(ses
.payload
);
430 termh
= buf_getint(ses
.payload
);
432 pty_change_window_size(chansess
->master
, termr
, termc
, termw
, termh
);
434 return DROPBEAR_SUCCESS
;
437 static void get_termmodes(struct ChanSess
*chansess
) {
439 struct termios termio
;
440 unsigned char opcode
;
442 const struct TermCode
* termcode
;
445 TRACE(("enter get_termmodes"))
448 /* We'll ignore errors and continue if we can't set modes.
449 * We're ignoring baud rates since they seem evil */
450 if (tcgetattr(chansess
->master
, &termio
) == -1) {
454 len
= buf_getint(ses
.payload
);
455 TRACE(("term mode str %d p->l %d p->p %d",
456 len
, ses
.payload
->len
, ses
.payload
->pos
));
457 if (len
!= ses
.payload
->len
- ses
.payload
->pos
) {
458 dropbear_exit("bad term mode string");
462 TRACE(("leave get_termmodes: empty terminal modes string"))
466 while (((opcode
= buf_getbyte(ses
.payload
)) != 0x00) && opcode
<= 159) {
468 /* must be before checking type, so that value is consumed even if
470 value
= buf_getint(ses
.payload
);
472 /* handle types of code */
473 if (opcode
> MAX_TERMCODE
) {
476 termcode
= &termcodes
[(unsigned int)opcode
];
479 switch (termcode
->type
) {
484 case TERMCODE_CONTROLCHAR
:
485 termio
.c_cc
[termcode
->mapcode
] = value
;
490 termio
.c_iflag
|= termcode
->mapcode
;
492 termio
.c_iflag
&= ~(termcode
->mapcode
);
496 case TERMCODE_OUTPUT
:
498 termio
.c_oflag
|= termcode
->mapcode
;
500 termio
.c_oflag
&= ~(termcode
->mapcode
);
506 termio
.c_lflag
|= termcode
->mapcode
;
508 termio
.c_lflag
&= ~(termcode
->mapcode
);
512 case TERMCODE_CONTROL
:
514 termio
.c_cflag
|= termcode
->mapcode
;
516 termio
.c_cflag
&= ~(termcode
->mapcode
);
522 if (tcsetattr(chansess
->master
, TCSANOW
, &termio
) < 0) {
523 dropbear_log(LOG_INFO
, "error setting terminal attributes");
525 TRACE(("leave get_termmodes"))
528 /* Set up a session pty which will be used to execute the shell or program.
529 * The pty is allocated now, and kept for when the shell/program executes.
530 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
531 static int sessionpty(struct ChanSess
* chansess
) {
533 unsigned int termlen
;
534 unsigned char namebuf
[65];
535 struct passwd
* pw
= NULL
;
537 TRACE(("enter sessionpty"))
539 if (!svr_pubkey_allows_pty()) {
540 TRACE(("leave sessionpty : pty forbidden by public key option"))
541 return DROPBEAR_FAILURE
;
544 chansess
->term
= buf_getstring(ses
.payload
, &termlen
);
545 if (termlen
> MAX_TERM_LEN
) {
546 /* TODO send disconnect ? */
547 TRACE(("leave sessionpty: term len too long"))
548 return DROPBEAR_FAILURE
;
551 /* allocate the pty */
552 if (chansess
->master
!= -1) {
553 dropbear_exit("multiple pty requests");
555 if (pty_allocate(&chansess
->master
, &chansess
->slave
, namebuf
, 64) == 0) {
556 TRACE(("leave sessionpty: failed to allocate pty"))
557 return DROPBEAR_FAILURE
;
560 chansess
->tty
= (char*)m_strdup(namebuf
);
561 if (!chansess
->tty
) {
562 dropbear_exit("out of memory"); /* TODO disconnect */
565 pw
= getpwnam(ses
.authstate
.pw_name
);
567 dropbear_exit("getpwnam failed after succeeding previously");
568 pty_setowner(pw
, chansess
->tty
);
570 /* Set up the rows/col counts */
571 sessionwinchange(chansess
);
573 /* Read the terminal modes */
574 get_termmodes(chansess
);
576 TRACE(("leave sessionpty"))
577 return DROPBEAR_SUCCESS
;
580 static char* make_connection_string() {
581 char *local_ip
, *local_port
, *remote_ip
, *remote_port
;
584 get_socket_address(ses
.sock_in
, &local_ip
, &local_port
, &remote_ip
, &remote_port
, 0);
585 len
= strlen(local_ip
) + strlen(local_port
) + strlen(remote_ip
) + strlen(remote_port
) + 4;
587 snprintf(ret
, len
, "%s %s %s %s", remote_ip
, remote_port
, local_ip
, local_port
);
595 /* Handle a command request from the client. This is used for both shell
596 * and command-execution requests, and passes the command to
597 * noptycommand or ptycommand as appropriate.
598 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
599 static int sessioncommand(struct Channel
*channel
, struct ChanSess
*chansess
,
600 int iscmd
, int issubsys
) {
605 TRACE(("enter sessioncommand"))
607 if (chansess
->cmd
!= NULL
) {
608 /* Note that only one command can _succeed_. The client might try
609 * one command (which fails), then try another. Ie fallback
610 * from sftp to scp */
611 return DROPBEAR_FAILURE
;
616 if (chansess
->cmd
== NULL
) {
617 chansess
->cmd
= buf_getstring(ses
.payload
, &cmdlen
);
619 if (cmdlen
> MAX_CMD_LEN
) {
620 m_free(chansess
->cmd
);
621 /* TODO - send error - too long ? */
622 return DROPBEAR_FAILURE
;
626 #ifdef SFTPSERVER_PATH
627 if ((cmdlen
== 4) && strncmp(chansess
->cmd
, "sftp", 4) == 0) {
628 m_free(chansess
->cmd
);
629 chansess
->cmd
= m_strdup(SFTPSERVER_PATH
);
633 m_free(chansess
->cmd
);
634 return DROPBEAR_FAILURE
;
639 /* take public key option 'command' into account */
640 svr_pubkey_set_forced_command(chansess
);
644 dropbear_log(LOG_INFO
, "user %s executing '%s'",
645 ses
.authstate
.pw_name
, chansess
->cmd
);
647 dropbear_log(LOG_INFO
, "user %s executing login shell",
648 ses
.authstate
.pw_name
);
652 /* uClinux will vfork(), so there'll be a race as
653 connection_string is freed below. */
655 chansess
->connection_string
= make_connection_string();
658 if (chansess
->term
== NULL
) {
660 ret
= noptycommand(channel
, chansess
);
663 ret
= ptycommand(channel
, chansess
);
667 m_free(chansess
->connection_string
);
670 if (ret
== DROPBEAR_FAILURE
) {
671 m_free(chansess
->cmd
);
676 /* Execute a command and set up redirection of stdin/stdout/stderr without a
678 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
679 static int noptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
682 TRACE(("enter noptycommand"))
683 ret
= spawn_command(execchild
, chansess
,
684 &channel
->writefd
, &channel
->readfd
, &channel
->errfd
,
687 if (ret
== DROPBEAR_FAILURE
) {
691 ses
.maxfd
= MAX(ses
.maxfd
, channel
->writefd
);
692 ses
.maxfd
= MAX(ses
.maxfd
, channel
->readfd
);
693 ses
.maxfd
= MAX(ses
.maxfd
, channel
->errfd
);
695 addchildpid(chansess
, chansess
->pid
);
697 if (svr_ses
.lastexit
.exitpid
!= -1) {
699 TRACE(("parent side: lastexitpid is %d", svr_ses
.lastexit
.exitpid
))
700 /* The child probably exited and the signal handler triggered
701 * possibly before we got around to adding the childpid. So we fill
702 * out its data manually */
703 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
704 if (svr_ses
.childpids
[i
].pid
== svr_ses
.lastexit
.exitpid
) {
705 TRACE(("found match for lastexitpid"))
706 svr_ses
.childpids
[i
].chansess
->exit
= svr_ses
.lastexit
;
707 svr_ses
.lastexit
.exitpid
= -1;
712 TRACE(("leave noptycommand"))
713 return DROPBEAR_SUCCESS
;
716 /* Execute a command or shell within a pty environment, and set up
717 * redirection as appropriate.
718 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
719 static int ptycommand(struct Channel
*channel
, struct ChanSess
*chansess
) {
722 struct logininfo
*li
= NULL
;
724 buffer
* motdbuf
= NULL
;
727 char *hushpath
= NULL
;
730 TRACE(("enter ptycommand"))
732 /* we need to have a pty allocated */
733 if (chansess
->master
== -1 || chansess
->tty
== NULL
) {
734 dropbear_log(LOG_WARNING
, "no pty was allocated, couldn't execute");
735 return DROPBEAR_FAILURE
;
744 return DROPBEAR_FAILURE
;
749 TRACE(("back to normal sigchld"))
750 /* Revert to normal sigchld handling */
751 if (signal(SIGCHLD
, SIG_DFL
) == SIG_ERR
) {
752 dropbear_exit("signal() error");
755 /* redirect stdin/stdout/stderr */
756 close(chansess
->master
);
758 pty_make_controlling_tty(&chansess
->slave
, chansess
->tty
);
760 if ((dup2(chansess
->slave
, STDIN_FILENO
) < 0) ||
761 (dup2(chansess
->slave
, STDERR_FILENO
) < 0) ||
762 (dup2(chansess
->slave
, STDOUT_FILENO
) < 0)) {
763 TRACE(("leave ptycommand: error redirecting filedesc"))
764 return DROPBEAR_FAILURE
;
767 close(chansess
->slave
);
769 /* write the utmp/wtmp login record - must be after changing the
770 * terminal used for stdout with the dup2 above */
771 li
= chansess_login_alloc(chansess
);
773 login_free_entry(li
);
776 if (svr_opts
.domotd
) {
777 /* don't show the motd if ~/.hushlogin exists */
779 /* 12 == strlen("/.hushlogin\0") */
780 len
= strlen(ses
.authstate
.pw_dir
) + 12;
782 hushpath
= m_malloc(len
);
783 snprintf(hushpath
, len
, "%s/.hushlogin", ses
.authstate
.pw_dir
);
785 if (stat(hushpath
, &sb
) < 0) {
786 /* more than a screenful is stupid IMHO */
787 motdbuf
= buf_new(80 * 25);
788 if (buf_readfile(motdbuf
, MOTD_FILENAME
) == DROPBEAR_SUCCESS
) {
789 buf_setpos(motdbuf
, 0);
790 while (motdbuf
->pos
!= motdbuf
->len
) {
791 len
= motdbuf
->len
- motdbuf
->pos
;
792 len
= write(STDOUT_FILENO
,
793 buf_getptr(motdbuf
, len
), len
);
794 buf_incrpos(motdbuf
, len
);
808 TRACE(("continue ptycommand: parent"))
811 /* add a child pid */
812 addchildpid(chansess
, pid
);
814 close(chansess
->slave
);
815 channel
->writefd
= chansess
->master
;
816 channel
->readfd
= chansess
->master
;
817 /* don't need to set stderr here */
818 ses
.maxfd
= MAX(ses
.maxfd
, chansess
->master
);
820 setnonblocking(chansess
->master
);
824 TRACE(("leave ptycommand"))
825 return DROPBEAR_SUCCESS
;
828 /* Add the pid of a child to the list for exit-handling */
829 static void addchildpid(struct ChanSess
*chansess
, pid_t pid
) {
832 for (i
= 0; i
< svr_ses
.childpidsize
; i
++) {
833 if (svr_ses
.childpids
[i
].pid
== -1) {
838 /* need to increase size */
839 if (i
== svr_ses
.childpidsize
) {
840 svr_ses
.childpids
= (struct ChildPid
*)m_realloc(svr_ses
.childpids
,
841 sizeof(struct ChildPid
) * (svr_ses
.childpidsize
+1));
842 svr_ses
.childpidsize
++;
845 svr_ses
.childpids
[i
].pid
= pid
;
846 svr_ses
.childpids
[i
].chansess
= chansess
;
850 /* Clean up, drop to user privileges, set up the environment and execute
851 * the command/shell. This function does not return. */
852 static void execchild(void *user_data
) {
853 struct ChanSess
*chansess
= user_data
;
854 char *usershell
= NULL
;
856 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
857 * hostkey. can't think of a workaround to clear it */
859 /* wipe the hostkey */
860 sign_key_free(svr_opts
.hostkey
);
861 svr_opts
.hostkey
= NULL
;
863 /* overwrite the prng state */
867 /* clear environment */
868 /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
869 * etc. This is hazardous, so should only be used for debugging. */
870 #ifndef DEBUG_VALGRIND
873 #else /* don't HAVE_CLEARENV */
878 #endif /* HAVE_CLEARENV */
879 #endif /* DEBUG_VALGRIND */
881 /* We can only change uid/gid as root ... */
884 if ((setgid(ses
.authstate
.pw_gid
) < 0) ||
885 (initgroups(ses
.authstate
.pw_name
,
886 ses
.authstate
.pw_gid
) < 0)) {
887 dropbear_exit("error changing user group");
889 if (setuid(ses
.authstate
.pw_uid
) < 0) {
890 dropbear_exit("error changing user");
893 /* ... but if the daemon is the same uid as the requested uid, we don't
896 /* XXX - there is a minor issue here, in that if there are multiple
897 * usernames with the same uid, but differing groups, then the
898 * differing groups won't be set (as with initgroups()). The solution
899 * is for the sysadmin not to give out the UID twice */
900 if (getuid() != ses
.authstate
.pw_uid
) {
901 dropbear_exit("couldn't change user as non-root");
906 addnewvar("USER", ses
.authstate
.pw_name
);
907 addnewvar("LOGNAME", ses
.authstate
.pw_name
);
908 addnewvar("HOME", ses
.authstate
.pw_dir
);
909 addnewvar("SHELL", get_user_shell());
910 addnewvar("PATH", DEFAULT_PATH
);
911 if (chansess
->term
!= NULL
) {
912 addnewvar("TERM", chansess
->term
);
916 addnewvar("SSH_TTY", chansess
->tty
);
919 if (chansess
->connection_string
) {
920 addnewvar("SSH_CONNECTION", chansess
->connection_string
);
923 #ifdef ENABLE_SVR_PUBKEY_OPTIONS
924 if (ses
.authstate
.pubkey_options
&&
925 ses
.authstate
.pubkey_options
->original_command
) {
926 addnewvar("SSH_ORIGINAL_COMMAND",
927 ses
.authstate
.pubkey_options
->original_command
);
931 /* change directory */
932 if (chdir(ses
.authstate
.pw_dir
) < 0) {
933 dropbear_exit("error changing directory");
936 #ifndef DISABLE_X11FWD
937 /* set up X11 forwarding if enabled */
938 x11setauth(chansess
);
940 #ifndef DISABLE_AGENTFWD
941 /* set up agent env variable */
942 svr_agentset(chansess
);
945 usershell
= m_strdup(get_user_shell());
946 run_shell_command(chansess
->cmd
, ses
.maxfd
, usershell
);
948 /* only reached on error */
949 dropbear_exit("child failed");
952 const struct ChanType svrchansess
= {
954 "session", /* name */
955 newchansess
, /* inithandler */
956 sesscheckclose
, /* checkclosehandler */
957 chansessionrequest
, /* reqhandler */
958 closechansess
, /* closehandler */
962 /* Set up the general chansession environment, in particular child-exit
964 void svr_chansessinitialise() {
966 struct sigaction sa_chld
;
968 /* single child process intially */
969 svr_ses
.childpids
= (struct ChildPid
*)m_malloc(sizeof(struct ChildPid
));
970 svr_ses
.childpids
[0].pid
= -1; /* unused */
971 svr_ses
.childpids
[0].chansess
= NULL
;
972 svr_ses
.childpidsize
= 1;
973 svr_ses
.lastexit
.exitpid
= -1; /* Nothing has exited yet */
974 sa_chld
.sa_handler
= sesssigchild_handler
;
975 sa_chld
.sa_flags
= SA_NOCLDSTOP
;
976 if (sigaction(SIGCHLD
, &sa_chld
, NULL
) < 0) {
977 dropbear_exit("signal() error");
982 /* add a new environment variable, allocating space for the entry */
983 void addnewvar(const char* param
, const char* var
) {
988 plen
= strlen(param
);
991 newvar
= m_malloc(plen
+ vlen
+ 2); /* 2 is for '=' and '\0' */
992 memcpy(newvar
, param
, plen
);
994 memcpy(&newvar
[plen
+1], var
, vlen
);
995 newvar
[plen
+vlen
+1] = '\0';
996 /* newvar is leaked here, but that's part of putenv()'s semantics */
997 if (putenv(newvar
) < 0) {
998 dropbear_exit("environ error");