comparison svr-chansession.c @ 391:00fcf5045160

propagate from branch 'au.asn.ucc.matt.ltc.dropbear' (head c1db4398d56c56c6d06ae1e20c1e0d04dbb598ed) to branch 'au.asn.ucc.matt.dropbear' (head d26d5eb2837f46b56a33fb0e7573aa0201abd4d5)
author Matt Johnston <matt@ucc.asn.au>
date Thu, 11 Jan 2007 04:29:08 +0000
parents a124aff0cbf1
children 59c7938af2bd
comparison
equal deleted inserted replaced
390:d8e44bef7917 391:00fcf5045160
1 /*
2 * Dropbear - a SSH2 server
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */
24
25 #include "includes.h"
26 #include "packet.h"
27 #include "buffer.h"
28 #include "session.h"
29 #include "dbutil.h"
30 #include "channel.h"
31 #include "chansession.h"
32 #include "sshpty.h"
33 #include "termcodes.h"
34 #include "ssh.h"
35 #include "random.h"
36 #include "utmp.h"
37 #include "x11fwd.h"
38 #include "agentfwd.h"
39 #include "runopts.h"
40
41 /* Handles sessions (either shells or programs) requested by the client */
42
43 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
44 int iscmd, int issubsys);
45 static int sessionpty(struct ChanSess * chansess);
46 static int sessionsignal(struct ChanSess *chansess);
47 static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
48 static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
49 static int sessionwinchange(struct ChanSess *chansess);
50 static void execchild(struct ChanSess *chansess);
51 static void addchildpid(struct ChanSess *chansess, pid_t pid);
52 static void sesssigchild_handler(int val);
53 static void closechansess(struct Channel *channel);
54 static int newchansess(struct Channel *channel);
55 static void chansessionrequest(struct Channel *channel);
56
57 static void send_exitsignalstatus(struct Channel *channel);
58 static void send_msg_chansess_exitstatus(struct Channel * channel,
59 struct ChanSess * chansess);
60 static void send_msg_chansess_exitsignal(struct Channel * channel,
61 struct ChanSess * chansess);
62 static int sesscheckclose(struct Channel *channel);
63 static void get_termmodes(struct ChanSess *chansess);
64
65
66 /* required to clear environment */
67 extern char** environ;
68
69 static int sesscheckclose(struct Channel *channel) {
70 return channel->writefd == -1;
71 }
72
73 /* Handler for childs exiting, store the state for return to the client */
74
75 /* There's a particular race we have to watch out for: if the forked child
76 * executes, exits, and this signal-handler is called, all before the parent
77 * gets to run, then the childpids[] array won't have the pid in it. Hence we
78 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
79 * the parent when it runs. This work correctly at least in the case of a
80 * single shell spawned (ie the usual case) */
81 static void sesssigchild_handler(int UNUSED(dummy)) {
82
83 int status;
84 pid_t pid;
85 unsigned int i;
86 struct sigaction sa_chld;
87 struct exitinfo *exit = NULL;
88
89 TRACE(("enter sigchld handler"))
90 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
91
92 exit = NULL;
93 /* find the corresponding chansess */
94 for (i = 0; i < svr_ses.childpidsize; i++) {
95 if (svr_ses.childpids[i].pid == pid) {
96
97 exit = &svr_ses.childpids[i].chansess->exit;
98 break;
99 }
100 }
101
102 /* If the pid wasn't matched, then we might have hit the race mentioned
103 * above. So we just store the info for the parent to deal with */
104 if (exit == NULL) {
105 exit = &svr_ses.lastexit;
106 }
107
108 exit->exitpid = pid;
109 if (WIFEXITED(status)) {
110 exit->exitstatus = WEXITSTATUS(status);
111 }
112 if (WIFSIGNALED(status)) {
113 exit->exitsignal = WTERMSIG(status);
114 #if !defined(AIX) && defined(WCOREDUMP)
115 exit->exitcore = WCOREDUMP(status);
116 #else
117 exit->exitcore = 0;
118 #endif
119 } else {
120 /* we use this to determine how pid exited */
121 exit->exitsignal = -1;
122 }
123 }
124
125
126 sa_chld.sa_handler = sesssigchild_handler;
127 sa_chld.sa_flags = SA_NOCLDSTOP;
128 sigaction(SIGCHLD, &sa_chld, NULL);
129 TRACE(("leave sigchld handler"))
130 }
131
132 /* send the exit status or the signal causing termination for a session */
133 /* XXX server */
134 static void send_exitsignalstatus(struct Channel *channel) {
135
136 struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
137
138 if (chansess->exit.exitpid >= 0) {
139 if (chansess->exit.exitsignal > 0) {
140 send_msg_chansess_exitsignal(channel, chansess);
141 } else {
142 send_msg_chansess_exitstatus(channel, chansess);
143 }
144 }
145 }
146
147 /* send the exitstatus to the client */
148 static void send_msg_chansess_exitstatus(struct Channel * channel,
149 struct ChanSess * chansess) {
150
151 dropbear_assert(chansess->exit.exitpid != -1);
152 dropbear_assert(chansess->exit.exitsignal == -1);
153
154 CHECKCLEARTOWRITE();
155
156 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
157 buf_putint(ses.writepayload, channel->remotechan);
158 buf_putstring(ses.writepayload, "exit-status", 11);
159 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
160 buf_putint(ses.writepayload, chansess->exit.exitstatus);
161
162 encrypt_packet();
163
164 }
165
166 /* send the signal causing the exit to the client */
167 static void send_msg_chansess_exitsignal(struct Channel * channel,
168 struct ChanSess * chansess) {
169
170 int i;
171 char* signame = NULL;
172
173 dropbear_assert(chansess->exit.exitpid != -1);
174 dropbear_assert(chansess->exit.exitsignal > 0);
175
176 CHECKCLEARTOWRITE();
177
178 /* we check that we can match a signal name, otherwise
179 * don't send anything */
180 for (i = 0; signames[i].name != NULL; i++) {
181 if (signames[i].signal == chansess->exit.exitsignal) {
182 signame = signames[i].name;
183 break;
184 }
185 }
186
187 if (signame == NULL) {
188 return;
189 }
190
191 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
192 buf_putint(ses.writepayload, channel->remotechan);
193 buf_putstring(ses.writepayload, "exit-signal", 11);
194 buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
195 buf_putstring(ses.writepayload, signame, strlen(signame));
196 buf_putbyte(ses.writepayload, chansess->exit.exitcore);
197 buf_putstring(ses.writepayload, "", 0); /* error msg */
198 buf_putstring(ses.writepayload, "", 0); /* lang */
199
200 encrypt_packet();
201 }
202
203 /* set up a session channel */
204 static int newchansess(struct Channel *channel) {
205
206 struct ChanSess *chansess;
207
208 dropbear_assert(channel->typedata == NULL);
209
210 chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
211 chansess->cmd = NULL;
212 chansess->pid = 0;
213
214 /* pty details */
215 chansess->master = -1;
216 chansess->slave = -1;
217 chansess->tty = NULL;
218 chansess->term = NULL;
219
220 chansess->exit.exitpid = -1;
221
222 channel->typedata = chansess;
223
224 #ifndef DISABLE_X11FWD
225 chansess->x11listener = NULL;
226 chansess->x11authprot = NULL;
227 chansess->x11authcookie = NULL;
228 #endif
229
230 #ifndef DISABLE_AGENTFWD
231 chansess->agentlistener = NULL;
232 chansess->agentfile = NULL;
233 chansess->agentdir = NULL;
234 #endif
235
236 return 0;
237
238 }
239
240 /* clean a session channel */
241 static void closechansess(struct Channel *channel) {
242
243 struct ChanSess *chansess;
244 unsigned int i;
245 struct logininfo *li;
246
247 chansess = (struct ChanSess*)channel->typedata;
248
249 send_exitsignalstatus(channel);
250
251 TRACE(("enter closechansess"))
252 if (chansess == NULL) {
253 TRACE(("leave closechansess: chansess == NULL"))
254 return;
255 }
256
257 m_free(chansess->cmd);
258 m_free(chansess->term);
259
260 if (chansess->tty) {
261 /* write the utmp/wtmp login record */
262 li = login_alloc_entry(chansess->pid, ses.authstate.username,
263 ses.remotehost, chansess->tty);
264 login_logout(li);
265 login_free_entry(li);
266
267 pty_release(chansess->tty);
268 m_free(chansess->tty);
269 }
270
271 #ifndef DISABLE_X11FWD
272 x11cleanup(chansess);
273 #endif
274
275 #ifndef DISABLE_AGENTFWD
276 agentcleanup(chansess);
277 #endif
278
279 /* clear child pid entries */
280 for (i = 0; i < svr_ses.childpidsize; i++) {
281 if (svr_ses.childpids[i].chansess == chansess) {
282 dropbear_assert(svr_ses.childpids[i].pid > 0);
283 TRACE(("closing pid %d", svr_ses.childpids[i].pid))
284 TRACE(("exitpid = %d", chansess->exit.exitpid))
285 svr_ses.childpids[i].pid = -1;
286 svr_ses.childpids[i].chansess = NULL;
287 }
288 }
289
290 m_free(chansess);
291
292 TRACE(("leave closechansess"))
293 }
294
295 /* Handle requests for a channel. These can be execution requests,
296 * or x11/authagent forwarding. These are passed to appropriate handlers */
297 static void chansessionrequest(struct Channel *channel) {
298
299 unsigned char * type = NULL;
300 unsigned int typelen;
301 unsigned char wantreply;
302 int ret = 1;
303 struct ChanSess *chansess;
304
305 TRACE(("enter chansessionrequest"))
306
307 type = buf_getstring(ses.payload, &typelen);
308 wantreply = buf_getbool(ses.payload);
309
310 if (typelen > MAX_NAME_LEN) {
311 TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
312 goto out;
313 }
314
315 chansess = (struct ChanSess*)channel->typedata;
316 dropbear_assert(chansess != NULL);
317 TRACE(("type is %s", type))
318
319 if (strcmp(type, "window-change") == 0) {
320 ret = sessionwinchange(chansess);
321 } else if (strcmp(type, "shell") == 0) {
322 ret = sessioncommand(channel, chansess, 0, 0);
323 } else if (strcmp(type, "pty-req") == 0) {
324 ret = sessionpty(chansess);
325 } else if (strcmp(type, "exec") == 0) {
326 ret = sessioncommand(channel, chansess, 1, 0);
327 } else if (strcmp(type, "subsystem") == 0) {
328 ret = sessioncommand(channel, chansess, 1, 1);
329 #ifndef DISABLE_X11FWD
330 } else if (strcmp(type, "x11-req") == 0) {
331 ret = x11req(chansess);
332 #endif
333 #ifndef DISABLE_AGENTFWD
334 } else if (strcmp(type, "[email protected]") == 0) {
335 ret = agentreq(chansess);
336 #endif
337 } else if (strcmp(type, "signal") == 0) {
338 ret = sessionsignal(chansess);
339 } else {
340 /* etc, todo "env", "subsystem" */
341 }
342
343 out:
344
345 if (wantreply) {
346 if (ret == DROPBEAR_SUCCESS) {
347 send_msg_channel_success(channel);
348 } else {
349 send_msg_channel_failure(channel);
350 }
351 }
352
353 m_free(type);
354 TRACE(("leave chansessionrequest"))
355 }
356
357
358 /* Send a signal to a session's process as requested by the client*/
359 static int sessionsignal(struct ChanSess *chansess) {
360
361 int sig = 0;
362 unsigned char* signame = NULL;
363 int i;
364
365 if (chansess->pid == 0) {
366 /* haven't got a process pid yet */
367 return DROPBEAR_FAILURE;
368 }
369
370 signame = buf_getstring(ses.payload, NULL);
371
372 i = 0;
373 while (signames[i].name != 0) {
374 if (strcmp(signames[i].name, signame) == 0) {
375 sig = signames[i].signal;
376 break;
377 }
378 i++;
379 }
380
381 m_free(signame);
382
383 if (sig == 0) {
384 /* failed */
385 return DROPBEAR_FAILURE;
386 }
387
388 if (kill(chansess->pid, sig) < 0) {
389 return DROPBEAR_FAILURE;
390 }
391
392 return DROPBEAR_SUCCESS;
393 }
394
395 /* Let the process know that the window size has changed, as notified from the
396 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
397 static int sessionwinchange(struct ChanSess *chansess) {
398
399 int termc, termr, termw, termh;
400
401 if (chansess->master < 0) {
402 /* haven't got a pty yet */
403 return DROPBEAR_FAILURE;
404 }
405
406 termc = buf_getint(ses.payload);
407 termr = buf_getint(ses.payload);
408 termw = buf_getint(ses.payload);
409 termh = buf_getint(ses.payload);
410
411 pty_change_window_size(chansess->master, termr, termc, termw, termh);
412
413 return DROPBEAR_SUCCESS;
414 }
415
416 static void get_termmodes(struct ChanSess *chansess) {
417
418 struct termios termio;
419 unsigned char opcode;
420 unsigned int value;
421 const struct TermCode * termcode;
422 unsigned int len;
423
424 TRACE(("enter get_termmodes"))
425
426 /* Term modes */
427 /* We'll ignore errors and continue if we can't set modes.
428 * We're ignoring baud rates since they seem evil */
429 if (tcgetattr(chansess->master, &termio) == -1) {
430 return;
431 }
432
433 len = buf_getint(ses.payload);
434 TRACE(("term mode str %d p->l %d p->p %d",
435 len, ses.payload->len , ses.payload->pos));
436 if (len != ses.payload->len - ses.payload->pos) {
437 dropbear_exit("bad term mode string");
438 }
439
440 if (len == 0) {
441 TRACE(("leave get_termmodes: empty terminal modes string"))
442 return;
443 }
444
445 while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
446
447 /* must be before checking type, so that value is consumed even if
448 * we don't use it */
449 value = buf_getint(ses.payload);
450
451 /* handle types of code */
452 if (opcode > MAX_TERMCODE) {
453 continue;
454 }
455 termcode = &termcodes[(unsigned int)opcode];
456
457
458 switch (termcode->type) {
459
460 case TERMCODE_NONE:
461 break;
462
463 case TERMCODE_CONTROLCHAR:
464 termio.c_cc[termcode->mapcode] = value;
465 break;
466
467 case TERMCODE_INPUT:
468 if (value) {
469 termio.c_iflag |= termcode->mapcode;
470 } else {
471 termio.c_iflag &= ~(termcode->mapcode);
472 }
473 break;
474
475 case TERMCODE_OUTPUT:
476 if (value) {
477 termio.c_oflag |= termcode->mapcode;
478 } else {
479 termio.c_oflag &= ~(termcode->mapcode);
480 }
481 break;
482
483 case TERMCODE_LOCAL:
484 if (value) {
485 termio.c_lflag |= termcode->mapcode;
486 } else {
487 termio.c_lflag &= ~(termcode->mapcode);
488 }
489 break;
490
491 case TERMCODE_CONTROL:
492 if (value) {
493 termio.c_cflag |= termcode->mapcode;
494 } else {
495 termio.c_cflag &= ~(termcode->mapcode);
496 }
497 break;
498
499 }
500 }
501 if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
502 dropbear_log(LOG_INFO, "error setting terminal attributes");
503 }
504 TRACE(("leave get_termmodes"))
505 }
506
507 /* Set up a session pty which will be used to execute the shell or program.
508 * The pty is allocated now, and kept for when the shell/program executes.
509 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
510 static int sessionpty(struct ChanSess * chansess) {
511
512 unsigned int termlen;
513 unsigned char namebuf[65];
514
515 TRACE(("enter sessionpty"))
516 chansess->term = buf_getstring(ses.payload, &termlen);
517 if (termlen > MAX_TERM_LEN) {
518 /* TODO send disconnect ? */
519 TRACE(("leave sessionpty: term len too long"))
520 return DROPBEAR_FAILURE;
521 }
522
523 /* allocate the pty */
524 if (chansess->master != -1) {
525 dropbear_exit("multiple pty requests");
526 }
527 if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
528 TRACE(("leave sessionpty: failed to allocate pty"))
529 return DROPBEAR_FAILURE;
530 }
531
532 chansess->tty = (char*)m_strdup(namebuf);
533 if (!chansess->tty) {
534 dropbear_exit("out of memory"); /* TODO disconnect */
535 }
536
537 pty_setowner(ses.authstate.pw, chansess->tty);
538
539 /* Set up the rows/col counts */
540 sessionwinchange(chansess);
541
542 /* Read the terminal modes */
543 get_termmodes(chansess);
544
545 TRACE(("leave sessionpty"))
546 return DROPBEAR_SUCCESS;
547 }
548
549 /* Handle a command request from the client. This is used for both shell
550 * and command-execution requests, and passes the command to
551 * noptycommand or ptycommand as appropriate.
552 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
553 static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
554 int iscmd, int issubsys) {
555
556 unsigned int cmdlen;
557 int ret;
558
559 TRACE(("enter sessioncommand"))
560
561 if (chansess->cmd != NULL) {
562 /* Note that only one command can _succeed_. The client might try
563 * one command (which fails), then try another. Ie fallback
564 * from sftp to scp */
565 return DROPBEAR_FAILURE;
566 }
567
568 if (iscmd) {
569 /* "exec" */
570 chansess->cmd = buf_getstring(ses.payload, &cmdlen);
571
572 if (cmdlen > MAX_CMD_LEN) {
573 m_free(chansess->cmd);
574 /* TODO - send error - too long ? */
575 return DROPBEAR_FAILURE;
576 }
577 if (issubsys) {
578 #ifdef SFTPSERVER_PATH
579 if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
580 m_free(chansess->cmd);
581 chansess->cmd = m_strdup(SFTPSERVER_PATH);
582 } else
583 #endif
584 {
585 m_free(chansess->cmd);
586 return DROPBEAR_FAILURE;
587 }
588 }
589 }
590
591 #ifdef LOG_COMMANDS
592 if (chansess->cmd) {
593 dropbear_log(LOG_INFO, "user %s executing '%s'",
594 ses.authstate.printableuser, chansess->cmd);
595 } else {
596 dropbear_log(LOG_INFO, "user %s executing login shell",
597 ses.authstate.printableuser);
598 }
599 #endif
600
601 if (chansess->term == NULL) {
602 /* no pty */
603 ret = noptycommand(channel, chansess);
604 } else {
605 /* want pty */
606 ret = ptycommand(channel, chansess);
607 }
608
609 if (ret == DROPBEAR_FAILURE) {
610 m_free(chansess->cmd);
611 }
612 return ret;
613 }
614
615 /* Execute a command and set up redirection of stdin/stdout/stderr without a
616 * pty.
617 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
618 static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
619
620 int infds[2];
621 int outfds[2];
622 int errfds[2];
623 pid_t pid;
624 unsigned int i;
625
626 TRACE(("enter noptycommand"))
627
628 /* redirect stdin/stdout/stderr */
629 if (pipe(infds) != 0)
630 return DROPBEAR_FAILURE;
631 if (pipe(outfds) != 0)
632 return DROPBEAR_FAILURE;
633 if (pipe(errfds) != 0)
634 return DROPBEAR_FAILURE;
635
636 #ifdef __uClinux__
637 pid = vfork();
638 #else
639 pid = fork();
640 #endif
641
642 if (pid < 0)
643 return DROPBEAR_FAILURE;
644
645 if (!pid) {
646 /* child */
647
648 /* redirect stdin/stdout */
649 #define FDIN 0
650 #define FDOUT 1
651 if ((dup2(infds[FDIN], STDIN_FILENO) < 0) ||
652 (dup2(outfds[FDOUT], STDOUT_FILENO) < 0) ||
653 (dup2(errfds[FDOUT], STDERR_FILENO) < 0)) {
654 TRACE(("leave noptycommand: error redirecting FDs"))
655 return DROPBEAR_FAILURE;
656 }
657
658 close(infds[FDOUT]);
659 close(infds[FDIN]);
660 close(outfds[FDIN]);
661 close(outfds[FDOUT]);
662 close(errfds[FDIN]);
663 close(errfds[FDOUT]);
664
665 execchild(chansess);
666 /* not reached */
667
668 } else {
669 /* parent */
670 TRACE(("continue noptycommand: parent"))
671 chansess->pid = pid;
672
673 addchildpid(chansess, pid);
674
675 if (svr_ses.lastexit.exitpid != -1) {
676 /* The child probably exited and the signal handler triggered
677 * possibly before we got around to adding the childpid. So we fill
678 * out it's data manually */
679 for (i = 0; i < svr_ses.childpidsize; i++) {
680 if (svr_ses.childpids[i].pid == pid) {
681 svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
682 svr_ses.lastexit.exitpid = -1;
683 }
684 }
685 }
686
687
688 close(infds[FDIN]);
689 close(outfds[FDOUT]);
690 close(errfds[FDOUT]);
691 channel->writefd = infds[FDOUT];
692 channel->readfd = outfds[FDIN];
693 channel->errfd = errfds[FDIN];
694 ses.maxfd = MAX(ses.maxfd, channel->writefd);
695 ses.maxfd = MAX(ses.maxfd, channel->readfd);
696 ses.maxfd = MAX(ses.maxfd, channel->errfd);
697
698 setnonblocking(channel->readfd);
699 setnonblocking(channel->writefd);
700 setnonblocking(channel->errfd);
701
702 }
703 #undef FDIN
704 #undef FDOUT
705
706 TRACE(("leave noptycommand"))
707 return DROPBEAR_SUCCESS;
708 }
709
710 /* Execute a command or shell within a pty environment, and set up
711 * redirection as appropriate.
712 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
713 static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
714
715 pid_t pid;
716 struct logininfo *li = NULL;
717 #ifdef DO_MOTD
718 buffer * motdbuf = NULL;
719 int len;
720 struct stat sb;
721 char *hushpath = NULL;
722 #endif
723
724 TRACE(("enter ptycommand"))
725
726 /* we need to have a pty allocated */
727 if (chansess->master == -1 || chansess->tty == NULL) {
728 dropbear_log(LOG_WARNING, "no pty was allocated, couldn't execute");
729 return DROPBEAR_FAILURE;
730 }
731
732 #ifdef __uClinux__
733 pid = vfork();
734 #else
735 pid = fork();
736 #endif
737 if (pid < 0)
738 return DROPBEAR_FAILURE;
739
740 if (pid == 0) {
741 /* child */
742
743 /* redirect stdin/stdout/stderr */
744 close(chansess->master);
745
746 pty_make_controlling_tty(&chansess->slave, chansess->tty);
747
748 if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
749 (dup2(chansess->slave, STDERR_FILENO) < 0) ||
750 (dup2(chansess->slave, STDOUT_FILENO) < 0)) {
751 TRACE(("leave ptycommand: error redirecting filedesc"))
752 return DROPBEAR_FAILURE;
753 }
754
755 close(chansess->slave);
756
757 /* write the utmp/wtmp login record - must be after changing the
758 * terminal used for stdout with the dup2 above */
759 li= login_alloc_entry(getpid(), ses.authstate.username,
760 ses.remotehost, chansess->tty);
761 login_login(li);
762 login_free_entry(li);
763
764 m_free(chansess->tty);
765
766 #ifdef DO_MOTD
767 if (svr_opts.domotd) {
768 /* don't show the motd if ~/.hushlogin exists */
769
770 /* 11 == strlen("/hushlogin\0") */
771 len = strlen(ses.authstate.pw->pw_dir) + 11;
772
773 hushpath = m_malloc(len);
774 snprintf(hushpath, len, "%s/hushlogin", ses.authstate.pw->pw_dir);
775
776 if (stat(hushpath, &sb) < 0) {
777 /* more than a screenful is stupid IMHO */
778 motdbuf = buf_new(80 * 25);
779 if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
780 buf_setpos(motdbuf, 0);
781 while (motdbuf->pos != motdbuf->len) {
782 len = motdbuf->len - motdbuf->pos;
783 len = write(STDOUT_FILENO,
784 buf_getptr(motdbuf, len), len);
785 buf_incrpos(motdbuf, len);
786 }
787 }
788 buf_free(motdbuf);
789 }
790 m_free(hushpath);
791 }
792 #endif /* DO_MOTD */
793
794 execchild(chansess);
795 /* not reached */
796
797 } else {
798 /* parent */
799 TRACE(("continue ptycommand: parent"))
800 chansess->pid = pid;
801
802 /* add a child pid */
803 addchildpid(chansess, pid);
804
805 close(chansess->slave);
806 channel->writefd = chansess->master;
807 channel->readfd = chansess->master;
808 /* don't need to set stderr here */
809 ses.maxfd = MAX(ses.maxfd, chansess->master);
810
811 setnonblocking(chansess->master);
812
813 }
814
815 TRACE(("leave ptycommand"))
816 return DROPBEAR_SUCCESS;
817 }
818
819 /* Add the pid of a child to the list for exit-handling */
820 static void addchildpid(struct ChanSess *chansess, pid_t pid) {
821
822 unsigned int i;
823 for (i = 0; i < svr_ses.childpidsize; i++) {
824 if (svr_ses.childpids[i].pid == -1) {
825 break;
826 }
827 }
828
829 /* need to increase size */
830 if (i == svr_ses.childpidsize) {
831 svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
832 sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
833 svr_ses.childpidsize++;
834 }
835
836 svr_ses.childpids[i].pid = pid;
837 svr_ses.childpids[i].chansess = chansess;
838
839 }
840
841 /* Clean up, drop to user privileges, set up the environment and execute
842 * the command/shell. This function does not return. */
843 static void execchild(struct ChanSess *chansess) {
844
845 char *argv[4];
846 char * usershell = NULL;
847 char * baseshell = NULL;
848 unsigned int i;
849
850 /* with uClinux we'll have vfork()ed, so don't want to overwrite the
851 * hostkey. can't think of a workaround to clear it */
852 #ifndef __uClinux__
853 /* wipe the hostkey */
854 sign_key_free(svr_opts.hostkey);
855 svr_opts.hostkey = NULL;
856
857 /* overwrite the prng state */
858 reseedrandom();
859 #endif
860
861 /* close file descriptors except stdin/stdout/stderr
862 * Need to be sure FDs are closed here to avoid reading files as root */
863 for (i = 3; i <= (unsigned int)ses.maxfd; i++) {
864 m_close(i);
865 }
866
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
871 #ifdef HAVE_CLEARENV
872 clearenv();
873 #else /* don't HAVE_CLEARENV */
874 /* Yay for posix. */
875 if (environ) {
876 environ[0] = NULL;
877 }
878 #endif /* HAVE_CLEARENV */
879 #endif /* DEBUG_VALGRIND */
880
881 /* We can only change uid/gid as root ... */
882 if (getuid() == 0) {
883
884 if ((setgid(ses.authstate.pw->pw_gid) < 0) ||
885 (initgroups(ses.authstate.pw->pw_name,
886 ses.authstate.pw->pw_gid) < 0)) {
887 dropbear_exit("error changing user group");
888 }
889 if (setuid(ses.authstate.pw->pw_uid) < 0) {
890 dropbear_exit("error changing user");
891 }
892 } else {
893 /* ... but if the daemon is the same uid as the requested uid, we don't
894 * need to */
895
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->pw_uid) {
901 dropbear_exit("couldn't change user as non-root");
902 }
903 }
904
905 /* an empty shell should be interpreted as "/bin/sh" */
906 if (ses.authstate.pw->pw_shell[0] == '\0') {
907 usershell = "/bin/sh";
908 } else {
909 usershell = ses.authstate.pw->pw_shell;
910 }
911
912 /* set env vars */
913 addnewvar("USER", ses.authstate.pw->pw_name);
914 addnewvar("LOGNAME", ses.authstate.pw->pw_name);
915 addnewvar("HOME", ses.authstate.pw->pw_dir);
916 addnewvar("SHELL", usershell);
917 if (chansess->term != NULL) {
918 addnewvar("TERM", chansess->term);
919 }
920
921 /* change directory */
922 if (chdir(ses.authstate.pw->pw_dir) < 0) {
923 dropbear_exit("error changing directory");
924 }
925
926 #ifndef DISABLE_X11FWD
927 /* set up X11 forwarding if enabled */
928 x11setauth(chansess);
929 #endif
930 #ifndef DISABLE_AGENTFWD
931 /* set up agent env variable */
932 agentset(chansess);
933 #endif
934
935 /* Re-enable SIGPIPE for the executed process */
936 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
937 dropbear_exit("signal() error");
938 }
939
940 baseshell = basename(usershell);
941
942 if (chansess->cmd != NULL) {
943 argv[0] = baseshell;
944 } else {
945 /* a login shell should be "-bash" for "/bin/bash" etc */
946 int len = strlen(baseshell) + 2; /* 2 for "-" */
947 argv[0] = (char*)m_malloc(len);
948 snprintf(argv[0], len, "-%s", baseshell);
949 }
950
951 if (chansess->cmd != NULL) {
952 argv[1] = "-c";
953 argv[2] = chansess->cmd;
954 argv[3] = NULL;
955 } else {
956 /* construct a shell of the form "-bash" etc */
957 argv[1] = NULL;
958 }
959
960 execv(usershell, argv);
961
962 /* only reached on error */
963 dropbear_exit("child failed");
964 }
965
966 const struct ChanType svrchansess = {
967 0, /* sepfds */
968 "session", /* name */
969 newchansess, /* inithandler */
970 sesscheckclose, /* checkclosehandler */
971 chansessionrequest, /* reqhandler */
972 closechansess, /* closehandler */
973 };
974
975
976 /* Set up the general chansession environment, in particular child-exit
977 * handling */
978 void svr_chansessinitialise() {
979
980 struct sigaction sa_chld;
981
982 /* single child process intially */
983 svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
984 svr_ses.childpids[0].pid = -1; /* unused */
985 svr_ses.childpids[0].chansess = NULL;
986 svr_ses.childpidsize = 1;
987 svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
988 sa_chld.sa_handler = sesssigchild_handler;
989 sa_chld.sa_flags = SA_NOCLDSTOP;
990 if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
991 dropbear_exit("signal() error");
992 }
993
994 }
995
996 /* add a new environment variable, allocating space for the entry */
997 void addnewvar(const char* param, const char* var) {
998
999 char* newvar = NULL;
1000 int plen, vlen;
1001
1002 plen = strlen(param);
1003 vlen = strlen(var);
1004
1005 newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
1006 memcpy(newvar, param, plen);
1007 newvar[plen] = '=';
1008 memcpy(&newvar[plen+1], var, vlen);
1009 newvar[plen+vlen+1] = '\0';
1010 /* newvar is leaked here, but that's part of putenv()'s semantics */
1011 if (putenv(newvar) < 0) {
1012 dropbear_exit("environ error");
1013 }
1014 }