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