server_session.c 22.5 KB
Newer Older
Niels Möller's avatar
Niels Möller committed
1 2 3 4 5 6 7
/* server_session.c
 *
 * $Id$
 */

/* lsh, an implementation of the ssh protocol
 *
8
 * Copyright (C) 1998, 2002 Niels Mller
Niels Möller's avatar
Niels Möller committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "server_session.h"

27
#include "channel_commands.h"
Niels Möller's avatar
Niels Möller committed
28
#include "format.h"
29
#include "read_data.h"
30
#include "reaper.h"
Niels Möller's avatar
Niels Möller committed
31
#include "server_pty.h"
32
#include "server_x11.h"
Niels Möller's avatar
Niels Möller committed
33 34 35 36 37 38 39 40 41 42
#include "ssh.h"
#include "tcpforward.h"
#include "translate_signal.h"
#include "tty.h"
#include "werror.h"
#include "xalloc.h"

#include <errno.h>

/* For debug */
43

Niels Möller's avatar
Niels Möller committed
44 45 46 47 48 49 50 51
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>

52
#include <signal.h>
Niels Möller's avatar
Niels Möller committed
53 54 55

#include "server_session.c.x"

56 57 58
/* Disabled in the 1.4 series. */
#undef WITH_X11_FORWARD
#define WITH_X11_FORWARD 0
Niels Möller's avatar
Niels Möller committed
59 60 61 62 63 64 65 66

/* Session */
/* GABA:
   (class
     (name server_session)
     (super ssh_channel)
     (vars
       ; User information
67
       ;; (user object lsh_user)
Niels Möller's avatar
Niels Möller committed
68

69
       (initial_window . UINT32)
Niels Möller's avatar
Niels Möller committed
70 71

       ; Resource to kill when the channel is closed. 
72
       (process object lsh_process)
Niels Möller's avatar
Niels Möller committed
73

74
       ; An allocated but not yet used pty
Niels Möller's avatar
Niels Möller committed
75
       (pty object pty_info)
76 77 78

       ; X11 forwarding.
       (x11 object server_x11_info)
79
       
Niels Möller's avatar
Niels Möller committed
80 81 82 83
       ; value of the TERM environment variable
       (term string)

       ; Child process's stdio 
84 85
       (in object lsh_fd)
       (out object lsh_fd)
86 87 88

       ; err may be NULL, if there's no separate stderr channel.
       ; This happens if we use a pty, and the bash workaround is used.
89
       (err object lsh_fd)))
Niels Möller's avatar
Niels Möller committed
90 91 92
*/

/* Receive channel data */
Niels Möller's avatar
Niels Möller committed
93 94 95
static void
do_receive(struct ssh_channel *c,
	   int type, struct lsh_string *data)
Niels Möller's avatar
Niels Möller committed
96 97 98 99 100 101
{
  CAST(server_session, closure, c);

  switch(type)
    {
    case CHANNEL_DATA:
102
      A_WRITE(&closure->in->write_buffer->super, data);
Niels Möller's avatar
Niels Möller committed
103
      break;
Niels Möller's avatar
Niels Möller committed
104 105 106
    case CHANNEL_STDERR_DATA:
      werror("Ignoring unexpected stderr data.\n");
      lsh_string_free(data);
Niels Möller's avatar
Niels Möller committed
107
      break;
Niels Möller's avatar
Niels Möller committed
108 109 110 111 112 113
    default:
      fatal("Internal error!\n");
    }
}

/* We may send more data */
Niels Möller's avatar
Niels Möller committed
114
static void
115 116
do_send_adjust(struct ssh_channel *s,
	       UINT32 i UNUSED)
Niels Möller's avatar
Niels Möller committed
117
{
118
  CAST(server_session, session, s);
Niels Möller's avatar
Niels Möller committed
119

120 121 122 123 124
  /* FIXME: Perhaps it's better to just check the read pointers, and
   * not bother with the alive-flags? */
  if (session->out->super.alive)
    {
      assert(session->out->read);
125

126
      lsh_oop_register_read_fd(session->out);
127 128 129
    }
  
  if (session->err && session->err->super.alive)
130
    {
131
      assert(session->err->read);
Niels Möller's avatar
Niels Möller committed
132
  
133
      lsh_oop_register_read_fd(session->err);
134
    }
Niels Möller's avatar
Niels Möller committed
135 136
}

Niels Möller's avatar
Niels Möller committed
137 138
static void
do_eof(struct ssh_channel *channel)
Niels Möller's avatar
Niels Möller committed
139 140 141
{
  CAST(server_session, session, channel);

142
  trace("server_session.c: do_eof\n");
143
  
Niels Möller's avatar
Niels Möller committed
144
  close_fd_write(session->in);
Niels Möller's avatar
Niels Möller committed
145 146
}

147
struct ssh_channel *
148
make_server_session(UINT32 initial_window,
149
		    struct alist *request_types)
Niels Möller's avatar
Niels Möller committed
150 151 152 153 154
{
  NEW(server_session, self);

  init_channel(&self->super);

155 156
  self->initial_window = initial_window;

Niels Möller's avatar
Niels Möller committed
157 158 159 160 161
  /* We don't want to receive any data before we have forked some
   * process to receive it. */
  self->super.rec_window_size = 0;

  /* FIXME: Make maximum packet size configurable. */
162
  self->super.rec_max_packet = SSH_MAX_PACKET;
Niels Möller's avatar
Niels Möller committed
163 164
  self->super.request_types = request_types;

165 166
  /* Note: We don't need a close handler; the channels resource list
   * is taken care of automatically. */
Niels Möller's avatar
Niels Möller committed
167 168
  
  self->process = NULL;
169 170 171

  self->pty = NULL;
  self->term = NULL;
Niels Möller's avatar
Niels Möller committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  
  self->in = NULL;
  self->out = NULL;
  self->err = NULL;
  
  return &self->super;
}


/* GABA:
   (class
     (name open_session)
     (super channel_open)
     (vars
       (session_requests object alist)))
*/

189
#define WINDOW_SIZE 10000
Niels Möller's avatar
Niels Möller committed
190

Niels Möller's avatar
Niels Möller committed
191 192 193
static void
do_open_session(struct channel_open *s,
		struct ssh_connection *connection UNUSED,
194
		struct channel_open_info *info UNUSED,
Niels Möller's avatar
Niels Möller committed
195 196 197
		struct simple_buffer *args,
		struct command_continuation *c,
		struct exception_handler *e)
Niels Möller's avatar
Niels Möller committed
198
{
199
  CAST(open_session, self, s);
Niels Möller's avatar
Niels Möller committed
200

201
  debug("server.c: do_open_session\n");
202 203

  assert(connection->user);
204
  
Niels Möller's avatar
Niels Möller committed
205 206
  if (parse_eod(args))
    {
Niels Möller's avatar
Niels Möller committed
207
      COMMAND_RETURN(c,
208
		     make_server_session(WINDOW_SIZE, self->session_requests));
Niels Möller's avatar
Niels Möller committed
209 210 211
    }
  else
    {
212
      PROTOCOL_ERROR(e, "trailing garbage in open message");
Niels Möller's avatar
Niels Möller committed
213 214 215
    }
}

216
struct channel_open *
217
make_open_session(struct alist *session_requests)
Niels Möller's avatar
Niels Möller committed
218 219 220 221 222 223 224 225 226 227
{
  NEW(open_session, closure);

  closure->super.handler = do_open_session;
  closure->session_requests = session_requests;
  
  return &closure->super;
}


228 229 230
struct lsh_string *
format_exit_signal(struct ssh_channel *channel,
		   int core, int signal)
Niels Möller's avatar
Niels Möller committed
231
{
232 233
  struct lsh_string *msg
    = ssh_format("%lz.\n", STRSIGNAL(signal));
Niels Möller's avatar
Niels Möller committed
234 235 236 237
  
  return format_channel_request(ATOM_EXIT_SIGNAL,
				channel,
				0,
238
				"%a%c%fS%z",
Niels Möller's avatar
Niels Möller committed
239 240 241 242 243
				signal_local_to_network(signal),
				core,
				msg, "");
}

244 245
struct lsh_string *
format_exit(struct ssh_channel *channel, int value)
Niels Möller's avatar
Niels Möller committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
{
  return format_channel_request(ATOM_EXIT_STATUS,
				channel,
				0,
				"%i", value);
}

/* GABA:
   (class
     (name exit_shell)
     (super exit_callback)
     (vars
       (session object server_session)))
*/

261 262 263
static void
do_exit_shell(struct exit_callback *c, int signaled,
	      int core, int value)
Niels Möller's avatar
Niels Möller committed
264 265 266 267 268
{
  CAST(exit_shell, closure, c);
  struct server_session *session = closure->session;
  struct ssh_channel *channel = &session->super;
  
269
  trace("server_session.c: do_exit_shell\n");
Niels Möller's avatar
Niels Möller committed
270
  
271
  /* NOTE: We don't close the child's stdio here. */
Niels Möller's avatar
Niels Möller committed
272 273 274

  if (!(channel->flags & CHANNEL_SENT_CLOSE))
    {
275 276 277 278
      verbose("server_session.c: Sending %a message on channel %i\n",
	      signaled ? ATOM_EXIT_SIGNAL : ATOM_EXIT_STATUS,
	      channel->channel_number);
      
279
      C_WRITE(channel->connection,
Niels Möller's avatar
Niels Möller committed
280 281
	      (signaled
	       ? format_exit_signal(channel, core, value)
282
	       : format_exit(channel, value)) );
Niels Möller's avatar
Niels Möller committed
283

284 285 286 287 288 289 290
      /* We want to close the channel as soon as all stdout and stderr
       * data has been sent. In particular, we don't wait for EOF from
       * the client, most clients never sends that. */
      
      channel->flags |= (CHANNEL_NO_WAIT_FOR_EOF | CHANNEL_CLOSE_AT_EOF);
      
      if (channel->flags & CHANNEL_SENT_EOF)
Niels Möller's avatar
Niels Möller committed
291 292
	{
	  /* We have sent EOF already, so initiate close */
Niels Möller's avatar
Niels Möller committed
293
	  channel_close(channel);
Niels Möller's avatar
Niels Möller committed
294 295 296 297
	}
    }
}

298 299
static struct exit_callback *
make_exit_shell(struct server_session *session)
Niels Möller's avatar
Niels Möller committed
300 301 302 303 304 305 306 307 308 309
{
  NEW(exit_shell, self);

  self->super.exit = do_exit_shell;
  self->session = session;

  return &self->super;
}


310 311
static int
make_pipes(int *in, int *out, int *err)
Niels Möller's avatar
Niels Möller committed
312 313 314
{
  int saved_errno;
  
315
  if (lsh_make_pipe(in))
Niels Möller's avatar
Niels Möller committed
316
    {
317
      if (lsh_make_pipe(out))
Niels Möller's avatar
Niels Möller committed
318
	{
319
	  if (lsh_make_pipe(err))
Niels Möller's avatar
Niels Möller committed
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
	    {
              return 1;
            }
	  saved_errno = errno;
          close(out[0]);
          close(out[1]);
        }
      else
	saved_errno = errno;
      close(in[0]);
      close(in[1]);
    }
  else
    saved_errno = errno;
  
  errno = saved_errno;
  return 0;
}

339 340
#define BASH_WORKAROUND 1

Niels Möller's avatar
Niels Möller committed
341 342
#if WITH_PTY_SUPPORT

343 344 345
/* Sets certain fd:s to -1, which means that the slave tty should be
 * used (for the child), or that the stdout fd should be duplicated
 * (for the parent). */
346 347 348
static int
make_pty(struct pty_info *pty, int *in, int *out, int *err)
{
Niels Möller's avatar
Niels Möller committed
349
  debug("make_pty... ");
350 351 352 353 354 355 356

  assert(pty);
  
  debug("exists: \n"
        "  alive = %i\n"
        "  master = %i\n"
        "... ",
357
        pty->super.alive, pty->master);
358 359
  debug("\n");
  
360
  if (pty) 
Niels Möller's avatar
Niels Möller committed
361
    {
362 363
      assert(pty->super.alive);
      
Niels Möller's avatar
Niels Möller committed
364
      debug("make_pty: Using allocated pty.\n");
365

366 367 368
      /* Ownership of the master fd is passed on to some file
       * object. We need an fd for window_change_request, but we have
       * to use our regular fd:s to the master side, or we're
369 370 371 372 373 374 375 376
       * disrupt EOF handling on either side. */

      pty->super.alive = 0;
      
      /* FIXME: It seems unnecessary to dup all the fd:s here. We
       * could use a single lsh_fd object for the master side of the
       * pty. */

377 378
      /* -1 means opening deferred to the child */
      in[0] = -1;
Niels Möller's avatar
Niels Möller committed
379
      in[1] = pty->master;
380
      
381 382 383 384
      if ((out[0] = dup(pty->master)) < 0)
        {
          werror("make_pty: duping master pty to stdout failed (errno = %i): %z\n",
                 errno, STRERROR(errno));
Niels Möller's avatar
Niels Möller committed
385

386 387
          return 0;
        }
Niels Möller's avatar
Niels Möller committed
388

389
      out[1] = -1;
390

391 392 393
      /* pty_info no longer owns the pty fd */
      pty->master = -1;

394
#if BASH_WORKAROUND
395 396
      /* Don't use a separate stderr channel; just dup the
       * stdout pty to stderr. */
397
            
398
      err[0] = -1;
399
      err[1] = -1;
400
      
401
#else /* !BASH_WORKAROUND */
402 403 404 405
      if (!lsh_make_pipe(err))
        {
          close(in[1]);
          close(out[0]);
406
	  
407 408 409 410
          return 0;
        }
#endif /* !BASH_WORKAROUND */
      return 1;
Niels Möller's avatar
Niels Möller committed
411 412 413
    }
  return 0;
}
414

Niels Möller's avatar
Niels Möller committed
415 416 417 418 419 420
#else /* !WITH_PTY_SUPPORT */
static int make_pty(struct pty_info *pty UNUSED,
		    int *in UNUSED, int *out UNUSED, int *err UNUSED)
{ return 0; }
#endif /* !WITH_PTY_SUPPORT */

421 422 423
static int
spawn_process(struct server_session *session,
	      struct lsh_user *user,
Niels Möller's avatar
Niels Möller committed
424
	      /* All information but the fd:s should be filled in
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
	       * already */
	      struct spawn_info *info)
{
  struct lsh_callback *read_close_callback;
  
  assert(!session->process);

  if (session->pty && !make_pty(session->pty,
				info->in, info->out, info->err))
    {
      KILL_RESOURCE(&session->pty->super);
      KILL(session->pty);
      session->pty = NULL;
    }

  if (!session->pty && !make_pipes(info->in, info->out, info->err))
    return 0;

  session->process = USER_SPAWN(user, info, make_exit_shell(session));

  if (!session->process)
    return 0;
  
  /* Close callback for stderr and stdout */
  read_close_callback
    = make_channel_read_close_callback(&session->super);
  
  session->in
453 454 455 456 457 458
    = io_write(make_lsh_fd
	       (info->in[1], "child stdin",
		make_channel_io_exception_handler(&session->super,
						  "Child stdin: ", 0,
						  &default_exception_handler,
						  HANDLER_CONTEXT)),
459
	       SSH_MAX_PACKET, NULL);
460 461 462 463

  if (session->pty)
    io_set_type(session->in, IO_PTY);
  
464 465 466 467 468 469 470
  /* Flow control */
  session->in->write_buffer->report = &session->super.super;
  
  /* FIXME: Should we really use the same exception handler,
   * which will close the channel on read errors, or is it
   * better to just send EOF on read errors? */
  session->out
471 472 473 474 475 476
    = io_read(make_lsh_fd
	      (info->out[0], "child stdout",
	       make_channel_io_exception_handler(&session->super,
						 "Child stdout: ", 1,
						 &default_exception_handler,
						 HANDLER_CONTEXT)),
477 478 479 480
	      make_channel_read_data(&session->super),
	      read_close_callback);
  session->err 
    = ( (info->err[0] != -1)
481 482 483 484 485 486
	? io_read(make_lsh_fd
		  (info->err[0], "child stderr",
		   make_channel_io_exception_handler(&session->super,
						     "Child stderr: ", 0,
						     &default_exception_handler,
						     HANDLER_CONTEXT)),
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
		  make_channel_read_stderr(&session->super),
		  read_close_callback)
	: NULL);

  session->super.receive = do_receive;
  session->super.send_adjust = do_send_adjust;
  session->super.eof = do_eof;
	  
  /* Make sure that the process and it's stdio is
   * cleaned up if the channel or connection dies. */
  remember_resource(session->super.resources, &session->process->super);

  /* FIXME: How to do this properly if in and out may use the
   * same fd? */
  remember_resource(session->super.resources, &session->in->super);
  remember_resource(session->super.resources, &session->out->super);
  if (session->err)
    remember_resource(session->super.resources, &session->err->super);

  /* Don't close channel immediately at EOF, as we want to
   * get a chance to send exit-status or exit-signal. */
  session->super.flags &= ~CHANNEL_CLOSE_AT_EOF;

  channel_start_receive(&session->super, session->initial_window);
  
  return 1;
}

static void
init_spawn_info(struct spawn_info *info, struct server_session *session,
		unsigned argc, const char **argv,
		unsigned env_length, struct env_value *env)
{
  unsigned i = 0;
  
  memset(info, 0, sizeof(*info));
  info->peer = session->super.connection->peer;
  info->pty = session->pty;

  info->argc = argc;
  info->argv = argv;
  
529
  assert(env_length >= 5);
530

531
  /* FIXME: Set SSH_ORIGINAL_COMMAND */
532 533 534 535 536 537
  if (session->term)
    {
      env[i].name ="TERM";
      env[i].value = session->term;
      i++;
    }
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563

  if (info->pty && info->pty->tty_name)
    {
      env[i].name ="SSH_TTY";
      env[i].value = info->pty->tty_name;
      i++;
    }

  if (info->peer)
    {
      env[i].name ="SSH_CLIENT";
      
      if (session->super.connection->local)
	env[i].value = ssh_format("%lS %di %di", 
				  info->peer->ip,
				  info->peer->port,
				  session->super.connection->local->port
				  );
      else
	env[i].value = ssh_format("%lS %di UNKNOWN", 
				  info->peer->ip,
				  info->peer->port
				  );	
      i++;
    }

564 565 566 567 568 569 570 571 572 573 574 575
#if WITH_X11_FORWARD
  if (session->x11)
    {
      env[i].name = "DISPLAY";
      env[i].value = session->x11->display;
      i++;

      env[i].name = "XAUTHORITY";
      env[i].value = session->x11->xauthority;
      i++;
    }
#endif /* WITH_X11_FORWARD */
576 577 578 579
  assert(i <= env_length);
  info->env_length = i;
  info->env = env;
}
580

Niels Möller's avatar
Niels Möller committed
581 582 583 584 585 586 587
DEFINE_CHANNEL_REQUEST(shell_request_handler)
     (struct channel_request *s UNUSED,
      struct ssh_channel *channel,
      struct channel_request_info *info UNUSED,
      struct simple_buffer *args,
      struct command_continuation *c,
      struct exception_handler *e)
Niels Möller's avatar
Niels Möller committed
588
{
589
  CAST(server_session, session, channel);
590
  struct spawn_info spawn;
591
  struct env_value env[5];
592 593
  
  static const struct exception shell_request_failed =
594 595
    STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "Shell request failed");

Niels Möller's avatar
Niels Möller committed
596
  if (!parse_eod(args))
Niels Möller's avatar
Niels Möller committed
597
    {
598
      PROTOCOL_ERROR(e, "Invalid shell CHANNEL_REQUEST message.");
Niels Möller's avatar
Niels Möller committed
599 600 601
      return;
    }
    
Niels Möller's avatar
Niels Möller committed
602 603 604 605
  if (session->process)
    /* Already spawned a shell or command */
    goto fail;

606
  init_spawn_info(&spawn, session, 0, NULL, 5, env);
607 608 609 610 611 612 613 614 615 616
  spawn.login = 1;
    
  if (spawn_process(session, channel->connection->user, &spawn))
    /* NOTE: The return value is not used. */
    COMMAND_RETURN(c, channel);
  else
    {
    fail:
      EXCEPTION_RAISE(e, &shell_request_failed);
    }
617
}
Niels Möller's avatar
Niels Möller committed
618

Niels Möller's avatar
Niels Möller committed
619 620 621 622 623 624 625
DEFINE_CHANNEL_REQUEST(exec_request_handler)
     (struct channel_request *s UNUSED,
      struct ssh_channel *channel,
      struct channel_request_info *info UNUSED,
      struct simple_buffer *args,
      struct command_continuation *c,
      struct exception_handler *e)
626 627
{
  CAST(server_session, session, channel);
Niels Möller's avatar
Niels Möller committed
628

629
  static const struct exception exec_request_failed =
630 631
    STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "Exec request failed");
  
632
  UINT32 command_len;
Niels Möller's avatar
Niels Möller committed
633
  const UINT8 *command;
634 635 636 637 638 639 640 641

  if (!(parse_string(args, &command_len, &command)
	&& parse_eod(args)))
    {
      PROTOCOL_ERROR(e, "Invalid exec CHANNEL_REQUEST message.");
      return;
    }
    
642 643 644 645 646
  if (/* Already spawned a shell or command */
      session->process
      /* Command can't contain NUL characters. */
      || memchr(command, '\0', command_len))
    
647 648 649
    EXCEPTION_RAISE(e, &exec_request_failed);
  else
    {
650 651
      struct spawn_info spawn;
      const char *args[2];
652
      struct env_value env[5];
653 654 655

      struct lsh_string *s = ssh_format("%ls", command_len, command);
      
656
      init_spawn_info(&spawn, session, 2, args, 5, env);
657
      spawn.login = 0;
658
      
659 660 661 662 663 664 665 666 667 668
      args[0] = "-c";
      args[1] = lsh_get_cstring(s);

      if (spawn_process(session, channel->connection->user, &spawn))
	/* NOTE: The return value is not used. */
	COMMAND_RETURN(c, channel);
      else
	EXCEPTION_RAISE(e, &exec_request_failed);

      lsh_string_free(s);
669
    }
Niels Möller's avatar
Niels Möller committed
670 671
}

672 673 674 675 676 677
/* For simplicity, represent a subsystem simply as a name of the
 * executable. */

/* GABA:
   (class
     (name subsystem_request)
Niels Möller's avatar
Niels Möller committed
678
     (super channel_request)
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
     (vars
       ;(subsystems object alist)
       ; A list { name, program, name, program, NULL }
       (subsystems . "const char **")))
*/

/* ;; GABA:
   (class
     (name sybsystem_info)
     (vars
       (name "const char *")))
*/

static const char *
lookup_subsystem(struct subsystem_request *self,
		 UINT32 length, const UINT8 *name)
{
  unsigned i;
  if (memchr(name, 0, length))
    return NULL;

  for (i = 0; self->subsystems[i]; i+=2)
    {
      assert(self->subsystems[i+1]);
      if ((length == strlen(self->subsystems[i]))
	  && !memcmp(name, self->subsystems[i], length))
	return self->subsystems[i + 1];
    }
  return NULL;
}

static void
do_spawn_subsystem(struct channel_request *s,
		   struct ssh_channel *channel,
		   struct channel_request_info *info UNUSED,
		   struct simple_buffer *args,
		   struct command_continuation *c,
		   struct exception_handler *e)
{
  CAST(subsystem_request, self, s);
  CAST(server_session, session, channel);

  static struct exception subsystem_request_failed =
    STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "Subsystem request failed");

  const UINT8 *name;
  UINT32 name_length;

  const char *program;
      
  if (! (parse_string(args, &name_length, &name) && parse_eod(args)))
    {
      PROTOCOL_ERROR(e, "Invalid subsystem CHANNEL_REQUEST message.");
      return;
    }
  
  program = lookup_subsystem(self, name_length, name);
  
  if (!session->process && program)
    {
739 740
      struct spawn_info spawn;
      const char *args[2];
741
      struct env_value env[5];
742

743 744 745 746 747 748
      /* Don't use any pty */
      if (session->pty)
	{
	  KILL_RESOURCE(&session->pty->super);
	  session->pty = NULL;
	}
749 750 751

      args[0] = "-c"; args[1] = program;
      
752
      init_spawn_info(&spawn, session, 2, args, 5, env);
753 754 755 756 757 758
      spawn.login = 0;

      if (spawn_process(session, channel->connection->user, &spawn))
	{
	  COMMAND_RETURN(c, channel);
	  return;
759
	}  
760 761 762 763 764
    }
  EXCEPTION_RAISE(e, &subsystem_request_failed);
}

struct channel_request *
765
make_subsystem_handler(const char **subsystems)
766 767 768
{
  NEW(subsystem_request, self);

Niels Möller's avatar
Niels Möller committed
769
  self->super.handler = do_spawn_subsystem;
770 771
  self->subsystems = subsystems;
  
Niels Möller's avatar
Niels Möller committed
772
  return &self->super;
773 774 775
}


Niels Möller's avatar
Niels Möller committed
776
#if WITH_PTY_SUPPORT
777

Niels Möller's avatar
Niels Möller committed
778
/* pty_handler */
Niels Möller's avatar
Niels Möller committed
779 780 781
static void
do_alloc_pty(struct channel_request *c UNUSED,
	     struct ssh_channel *channel,
Niels Möller's avatar
Niels Möller committed
782
	     struct channel_request_info *info UNUSED,
783 784 785
	     struct simple_buffer *args,
	     struct command_continuation *s,
	     struct exception_handler *e)
Niels Möller's avatar
Niels Möller committed
786 787 788
{
  struct lsh_string *term = NULL;

789 790 791
  static struct exception pty_request_failed =
    STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "pty request failed");

792 793
  struct pty_info *pty = make_pty_info();
  
794
  CAST(server_session, session, channel);
Niels Möller's avatar
Niels Möller committed
795 796 797

  verbose("Client requesting a tty...\n");

798
  if ((term = parse_string_copy(args))
799 800 801 802 803
      && parse_uint32(args, &pty->dims.char_width)
      && parse_uint32(args, &pty->dims.char_height)
      && parse_uint32(args, &pty->dims.pixel_width)
      && parse_uint32(args, &pty->dims.pixel_height)
      && (pty->mode = parse_string_copy(args))
804 805
      && parse_eod(args))
    {
806 807
      /* The client may only request one tty, and only before
       * starting a process. */
808 809
      if (session->pty || session->process
	  || !pty_open_master(pty, channel->connection->user->uid))
810
	{
811
	  verbose("Pty allocation failed.\n");
812 813 814 815
	  EXCEPTION_RAISE(e, &pty_request_failed);
	}
      else
	{
816 817 818
	  /* FIXME: Perhaps we can set the window dimensions directly
	   * on the master pty? */
	  session->term = term;
819
	  session->pty = pty;
820
	  remember_resource(channel->resources, &pty->super);
821 822 823 824 825 826

	  verbose(" granted.\n");
	  COMMAND_RETURN(s, NULL);

	  /* Success */
	  return;
827
	}
828

Niels Möller's avatar
Niels Möller committed
829
    }
830 831 832 833 834
  else
    {
      werror("Invalid pty request.\n");
      PROTOCOL_ERROR(e, "Invalid pty request.");
    }
835 836 837 838
  /* Cleanup for failure cases. */
  lsh_string_free(term);
  KILL_RESOURCE(&pty->super);
  KILL(pty);
Niels Möller's avatar
Niels Möller committed
839 840
}

841 842 843
struct channel_request
pty_request_handler =
{ STATIC_HEADER, do_alloc_pty };
Niels Möller's avatar
Niels Möller committed
844

845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
static void
do_window_change_request(struct channel_request *c UNUSED,
			 struct ssh_channel *channel,
			 struct channel_request_info *info UNUSED,
			 struct simple_buffer *args,
			 struct command_continuation *s,
			 struct exception_handler *e)
{
  struct terminal_dimensions dims;
  CAST(server_session, session, channel);

  verbose("Receiving window-change request...\n");

  if (parse_uint32(args, &dims.char_width)
      && parse_uint32(args, &dims.char_height)
      && parse_uint32(args, &dims.pixel_width)
      && parse_uint32(args, &dims.pixel_height)
      && parse_eod(args))
    {
      static const struct exception winch_request_failed =
	STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "window-change request failed: No pty");

867 868 869 870
      if (session->pty && session->in && session->in->super.alive
          && tty_setwinsize(session->in->fd, &dims))
        /* Success. Rely on the terminal driver sending SIGWINCH */
        COMMAND_RETURN(s, NULL);
871
      else
872
        EXCEPTION_RAISE(e, &winch_request_failed);
873 874
    }
  else
875
    PROTOCOL_ERROR(channel->connection->e, "Invalid window-change request.");
876 877 878 879 880 881
}

struct channel_request
window_change_request_handler =
{ STATIC_HEADER, do_window_change_request };

Niels Möller's avatar
Niels Möller committed
882
#endif /* WITH_PTY_SUPPORT */
883 884 885

#if WITH_X11_FORWARD

886 887
/* FIXME: We must delay the handling of any shell request until
 * we have responded to the x11-req message. */
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
static void
do_x11_req(struct channel_request *s UNUSED,
	   struct ssh_channel *channel,
	   struct channel_request_info *info UNUSED,
	   struct simple_buffer *args,
	   struct command_continuation *c,
	   struct exception_handler *e)
{
  static struct exception x11_request_failed =
    STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "x11-req failed");

  const UINT8 *protocol;
  UINT32 protocol_length;
  const UINT8 *cookie;
  UINT32 cookie_length;
  UINT32 screen;
904
  unsigned single;
905 906 907 908 909
  
  CAST(server_session, session, channel);

  verbose("Client requesting x11 forwarding...\n");

910
  if (parse_uint8(args, &single)
911 912 913 914 915 916 917
      && parse_string(args, &protocol_length, &protocol)
      && parse_string(args, &cookie_length, &cookie)
      && parse_uint32(args, &screen))
    {
      /* The client may only request one x11-forwarding, and only
       * before starting a process. */
      if (session->x11 || session->process
918 919 920 921 922
	  || !(session->x11 = server_x11_setup(channel,
					       channel->connection->user,
					       protocol_length, protocol,
					       cookie_length, cookie,
					       screen, c, e)))
923 924 925 926 927 928
	{
	  verbose("X11 request failed.\n");
	  EXCEPTION_RAISE(e, &x11_request_failed);
	}
      else
	{
929
	  
930 931 932 933 934
	  return;
	}
    }
  else
    {
935 936
      werror("Invalid x11 request.\n");
      PROTOCOL_ERROR(e, "Invalid x11 request.");
937 938 939 940 941 942 943 944
    }
}

struct channel_request
x11_req_handler =
{ STATIC_HEADER, do_x11_req };

#endif /* WITH_X11_FORWARD */