dropbear 2016.73
[tomato.git] / release / src / router / dropbear / svr-chansession.c
blobbfaf7f695f1e8e77d9228cb7da4ae5a3ba8a1a72
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:
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
23 * SOFTWARE. */
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 "dbrandom.h"
36 #include "x11fwd.h"
37 #include "agentfwd.h"
38 #include "runopts.h"
39 #include "auth.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 = {
66 0, /* sepfds */
67 "session", /* name */
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)) {
93 int status;
94 pid_t pid;
95 unsigned int i;
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))
108 exit = NULL;
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;
114 break;
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 */
120 if (exit == NULL) {
121 TRACE(("using lastexit"));
122 exit = &svr_ses.lastexit;
125 exit->exitpid = pid;
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);
133 #else
134 exit->exitcore = 0;
135 #endif
136 } else {
137 /* we use this to determine how pid exited */
138 exit->exitsignal = -1;
141 /* Make sure that the main select() loop wakes up */
142 while (1) {
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
146 || errno != EINTR) {
147 break;
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"))
158 errno = saved_errno;
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);
169 } else {
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);
182 CHECKCLEARTOWRITE();
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);
190 encrypt_packet();
194 /* send the signal causing the exit to the client */
195 static void send_msg_chansess_exitsignal(struct Channel * channel,
196 struct ChanSess * chansess) {
198 int i;
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))
205 CHECKCLEARTOWRITE();
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;
212 break;
216 if (signame == NULL) {
217 return;
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 */
229 encrypt_packet();
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;
245 chansess->pid = 0;
247 /* pty details */
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;
261 #endif
263 #ifdef ENABLE_SVR_AGENTFWD
264 chansess->agentlistener = NULL;
265 chansess->agentfile = NULL;
266 chansess->agentdir = NULL;
267 #endif
269 channel->prio = DROPBEAR_CHANNEL_PRIO_INTERACTIVE;
271 return 0;
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);
280 return li;
283 /* clean a session channel */
284 static void closechansess(struct Channel *channel) {
286 struct ChanSess *chansess;
287 unsigned int i;
288 struct logininfo *li;
290 TRACE(("enter closechansess"))
292 chansess = (struct ChanSess*)channel->typedata;
294 if (chansess == NULL) {
295 TRACE(("leave closechansess: chansess == NULL"))
296 return;
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);
306 #endif
308 if (chansess->tty) {
309 /* write the utmp/wtmp login record */
310 li = chansess_login_alloc(chansess);
311 login_logout(li);
312 login_free_entry(li);
314 pty_release(chansess->tty);
315 m_free(chansess->tty);
318 #ifndef DISABLE_X11FWD
319 x11cleanup(chansess);
320 #endif
322 #ifdef ENABLE_SVR_AGENTFWD
323 svr_agentcleanup(chansess);
324 #endif
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;
337 m_free(chansess);
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) {
346 char * type = NULL;
347 unsigned int typelen;
348 unsigned char wantreply;
349 int ret = 1;
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?*/
359 goto out;
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);
379 #endif
380 #ifdef ENABLE_SVR_AGENTFWD
381 } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
382 ret = svr_agentreq(chansess);
383 #endif
384 } else if (strcmp(type, "signal") == 0) {
385 ret = sessionsignal(chansess);
386 } else {
387 /* etc, todo "env", "subsystem" */
390 out:
392 if (wantreply) {
393 if (ret == DROPBEAR_SUCCESS) {
394 send_msg_channel_success(channel);
395 } else {
396 send_msg_channel_failure(channel);
400 m_free(type);
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) {
408 int sig = 0;
409 char* signame = NULL;
410 int i;
412 if (chansess->pid == 0) {
413 /* haven't got a process pid yet */
414 return DROPBEAR_FAILURE;
417 signame = buf_getstring(ses.payload, NULL);
419 i = 0;
420 while (signames[i].name != 0) {
421 if (strcmp(signames[i].name, signame) == 0) {
422 sig = signames[i].signal;
423 break;
425 i++;
428 m_free(signame);
430 if (sig == 0) {
431 /* failed */
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;
467 unsigned int value;
468 const struct TermCode * termcode;
469 unsigned int len;
471 TRACE(("enter get_termmodes"))
473 /* Term modes */
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) {
477 return;
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");
487 if (len == 0) {
488 TRACE(("leave get_termmodes: empty terminal modes string"))
489 return;
492 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
494 /* must be before checking type, so that value is consumed even if
495 * we don't use it */
496 value = buf_getint(ses.payload);
498 /* handle types of code */
499 if (opcode > MAX_TERMCODE) {
500 continue;
502 termcode = &termcodes[(unsigned int)opcode];
505 switch (termcode->type) {
507 case TERMCODE_NONE:
508 break;
510 case TERMCODE_CONTROLCHAR:
511 termio.c_cc[termcode->mapcode] = value;
512 break;
514 case TERMCODE_INPUT:
515 if (value) {
516 termio.c_iflag |= termcode->mapcode;
517 } else {
518 termio.c_iflag &= ~(termcode->mapcode);
520 break;
522 case TERMCODE_OUTPUT:
523 if (value) {
524 termio.c_oflag |= termcode->mapcode;
525 } else {
526 termio.c_oflag &= ~(termcode->mapcode);
528 break;
530 case TERMCODE_LOCAL:
531 if (value) {
532 termio.c_lflag |= termcode->mapcode;
533 } else {
534 termio.c_lflag &= ~(termcode->mapcode);
536 break;
538 case TERMCODE_CONTROL:
539 if (value) {
540 termio.c_cflag |= termcode->mapcode;
541 } else {
542 termio.c_cflag &= ~(termcode->mapcode);
544 break;
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;
560 char namebuf[65];
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);
592 if (!pw)
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;
606 #ifndef USE_VFORK
607 static void make_connection_string(struct ChanSess *chansess) {
608 char *local_ip, *local_port, *remote_ip, *remote_port;
609 size_t len;
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);
623 m_free(local_ip);
624 m_free(local_port);
625 m_free(remote_ip);
626 m_free(remote_port);
628 #endif
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) {
637 unsigned int cmdlen;
638 int ret;
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;
649 if (iscmd) {
650 /* "exec" */
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;
660 if (issubsys) {
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);
665 } else
666 #endif
668 m_free(chansess->cmd);
669 return DROPBEAR_FAILURE;
674 /* take public key option 'command' into account */
675 svr_pubkey_set_forced_command(chansess);
677 #ifdef LOG_COMMANDS
678 if (chansess->cmd) {
679 dropbear_log(LOG_INFO, "User %s executing '%s'",
680 ses.authstate.pw_name, chansess->cmd);
681 } else {
682 dropbear_log(LOG_INFO, "User %s executing login shell",
683 ses.authstate.pw_name);
685 #endif
687 /* uClinux will vfork(), so there'll be a race as
688 connection_string is freed below. */
689 #ifndef USE_VFORK
690 make_connection_string(chansess);
691 #endif
693 if (chansess->term == NULL) {
694 /* no pty */
695 ret = noptycommand(channel, chansess);
696 if (ret == DROPBEAR_SUCCESS) {
697 channel->prio = DROPBEAR_CHANNEL_PRIO_BULK;
698 update_channel_prio();
700 } else {
701 /* want pty */
702 ret = ptycommand(channel, chansess);
705 #ifndef USE_VFORK
706 m_free(chansess->connection_string);
707 m_free(chansess->client_string);
708 #endif
710 if (ret == DROPBEAR_FAILURE) {
711 m_free(chansess->cmd);
713 return ret;
716 /* Execute a command and set up redirection of stdin/stdout/stderr without a
717 * pty.
718 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
719 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
720 int ret;
722 TRACE(("enter noptycommand"))
723 ret = spawn_command(execchild, chansess,
724 &channel->writefd, &channel->readfd, &channel->errfd,
725 &chansess->pid);
727 if (ret == DROPBEAR_FAILURE) {
728 return ret;
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) {
738 unsigned int i;
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;
748 break;
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) {
762 pid_t pid;
763 struct logininfo *li = NULL;
764 #ifdef DO_MOTD
765 buffer * motdbuf = NULL;
766 int len;
767 struct stat sb;
768 char *hushpath = NULL;
769 #endif
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;
779 #ifdef USE_VFORK
780 pid = vfork();
781 #else
782 pid = fork();
783 #endif
784 if (pid < 0)
785 return DROPBEAR_FAILURE;
787 if (pid == 0) {
788 /* child */
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);
813 login_login(li);
814 login_free_entry(li);
816 #ifdef DO_MOTD
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);
838 buf_free(motdbuf);
840 m_free(hushpath);
842 #endif /* DO_MOTD */
844 execchild(chansess);
845 /* not reached */
847 } else {
848 /* parent */
849 TRACE(("continue ptycommand: parent"))
850 chansess->pid = pid;
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) {
872 unsigned int i;
873 for (i = 0; i < svr_ses.childpidsize; i++) {
874 if (svr_ses.childpids[i].pid == -1) {
875 break;
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 */
899 #ifndef USE_VFORK
900 /* wipe the hostkey */
901 sign_key_free(svr_opts.hostkey);
902 svr_opts.hostkey = NULL;
904 /* overwrite the prng state */
905 seedrandom();
906 #endif
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
912 #ifdef HAVE_CLEARENV
913 clearenv();
914 #else /* don't HAVE_CLEARENV */
915 /* Yay for posix. */
916 if (environ) {
917 environ[0] = NULL;
919 #endif /* HAVE_CLEARENV */
920 #endif /* DEBUG_VALGRIND */
922 /* We can only change uid/gid as root ... */
923 if (getuid() == 0) {
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");
933 } else {
934 /* ... but if the daemon is the same uid as the requested uid, we don't
935 * need to */
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");
946 /* set env vars */
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);
956 if (chansess->tty) {
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);
972 #endif
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);
982 #endif
983 #ifdef ENABLE_SVR_AGENTFWD
984 /* set up agent env variable */
985 svr_agentset(chansess);
986 #endif
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
996 * handling */
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;
1020 int plen, vlen;
1022 plen = strlen(param);
1023 vlen = strlen(var);
1025 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
1026 memcpy(newvar, param, plen);
1027 newvar[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");