lsh.c 31.6 KB
Newer Older
Niels Möller's avatar
Niels Möller committed
1
2
/* lsh.c
 *
3
 * Client main program.
4
 *
5
 */
6
7
8

/* lsh, an implementation of the ssh protocol
 *
9
 * Copyright (C) 1998, 1999, 2000, 2005 Niels Mller
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * 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
J.H.M. Dassen's avatar
J.H.M. Dassen committed
23
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Niels Möller's avatar
Niels Möller committed
24
25
 */

26
27
28
29
30
31
32
33
34
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <assert.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
Niels Möller's avatar
Niels Möller committed
35
#include <string.h>
36
37
38
39
40
41

#include <fcntl.h>

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

42
43
44
45
#include "nettle/sexp.h"
/* For struct spki_iterator */
#include "spki/parse.h"

Niels Möller's avatar
Niels Möller committed
46
#include "alist.h"
47
#include "arglist.h"
Niels Möller's avatar
Niels Möller committed
48
#include "atoms.h"
49
#include "channel.h"
50
#include "charset.h"
Niels Möller's avatar
Niels Möller committed
51
#include "client.h"
52
#include "compress.h"
Niels Möller's avatar
Niels Möller committed
53
#include "crypto.h"
54
#include "environ.h"
Niels Möller's avatar
Niels Möller committed
55
#include "format.h"
56
#include "interact.h"
57
#include "gateway.h"
58
#include "lsh_string.h"
Niels Möller's avatar
Niels Möller committed
59
#include "reaper.h"
60
61
#include "sexp.h"
#include "ssh.h"
62
#include "ssh_write.h"
63
#include "tcpforward.h"
64
#include "version.h"
Niels Möller's avatar
Niels Möller committed
65
#include "werror.h"
Niels Möller's avatar
Niels Möller committed
66
#include "xalloc.h"
Niels Möller's avatar
Niels Möller committed
67

68
#include "lsh_argp.h"
69

70
71
#include "lsh.c.x"

72
73
74
#define DEFAULT_ESCAPE_CHAR '~'
#define DEFAULT_SOCKS_PORT 1080

75
76
77
78
79
80
81
82
/* Flow control status: If the buffer for writing to the transport
   layer gets full, we stop reading on all channels, and we stop
   reading from all gateways. FIXME: Missing pieces:

   1. If a channel is somewhere in the opening handshake when we
      detect the transport buffer getting full, it is not signalled to
      stop, and might start generating data when the handshake is
      finished.
Niels Möller's avatar
Niels Möller committed
83

84
*/
85

86

87
/* Block size for stdout and stderr buffers */
Niels Möller's avatar
Niels Möller committed
88
89
#define BLOCK_SIZE 32768

90
91
92
93
/* Window size for the session channel
 *
 * NOTE: Large windows seem to trig a bug in sshd2. */
#define WINDOW_SIZE 10000
94

95
96
97
/* GABA:
   (class
     (name lsh_options)
98
     (super werror_config)
99
100
     (vars
       (home . "const char *")
101

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
       ; -1 means default.
       (escape . int)
       
       (exit_code . "int *")

       (not . int)
       (port . "const char *")
       (target . "const char *")

       (local_user . "char *")
       (user . "char *")

       (with_remote_peers . int)
       
       ; -1 means default behaviour
       (with_pty . int)

       (with_x11 . int)
       
       ; Session modifiers
       (stdin_file . "const char *")
       (stdout_file . "const char *")
       (stderr_file . "const char *")

       ; True if the process's stdin or pty (respectively) has been used. 
       (used_stdin . int)
       (used_pty . int)

130
       (detach . int)
131
132
133
134
135
136
137
138
139
140
141
       ; Should -B write the pid to stdout?
       (write_pid . int)
       
       ; True if the client should detach when a session closes (useful for gateways)
       (detach_end . int)

       ; Inhibit actions, used to not create actions from environment parsing.
       (inhibit_actions . int)

       (start_shell . int)
       (remote_forward . int)
142
       (x11_forward . int)
143
144
       (actions struct object_queue)

145
146
147
148
149
150
151
152
       ; 0 means no, 1 means yes, -1 means use if available.
       (use_gateway . int)
       ; 0 means no, 1 means yes, -1 means start if not already available.
       (start_gateway . int)
       (stop_gateway . int)

       (gateway object local_info)

153
154
155
156
157
       (transport_args . "struct arglist")

       ; Resources that are created during argument parsing. These should be adopted
       ; by the connection once it is up and running.
       (resources object resource_list)))
158
159
160
161
*/


static struct lsh_options *
162
make_options(int *exit_code)
163
164
{
  NEW(lsh_options, self);
165
  const char *home = getenv(ENV_HOME);
166
  const char *transport_program;
167

168
  init_werror_config(&self->super);
169

Niels Möller's avatar
Niels Möller committed
170
  self->home = home;
171

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
  self->escape = -1;

  self->exit_code = exit_code;

  self->not = 0;
  self->port = NULL;
  self->target = NULL;

  USER_NAME_FROM_ENV(self->user);
  self->local_user = self->user;

  self->with_remote_peers = 0;
  self->with_pty = -1;
  self->with_x11 = 0;

  self->stdin_file = NULL;
  self->stdout_file = NULL;
  self->stderr_file = NULL;

  self->used_stdin = 0;
  self->used_pty = 0;

194
  self->detach = 0;
195
196
197
198
  self->detach_end = 0;
  self->write_pid = 0;

  self->start_shell = 1;
199
  self->x11_forward = 0;
200
201
202
203
204
205
  self->remote_forward = 0;

  self->inhibit_actions = 0;

  object_queue_init(&self->actions);

206
207
208
209
  self->use_gateway = -1;
  self->start_gateway = 0;
  self->stop_gateway = 0;
  self->gateway = NULL;
210

211
212
213
214
215
  arglist_init(&self->transport_args);

  /* Set argv[0] */
  GET_FILE_ENV(transport_program, LSH_TRANSPORT);
  arglist_push(&self->transport_args, transport_program);
216
217
218
219

  self->resources = make_resource_list();
  gc_global(&self->resources->super);

Niels Möller's avatar
Niels Möller committed
220
  return self;
221
222
}

223
224
static void
add_action(struct lsh_options *options,
225
	   struct client_connection_action *action)
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
{
  assert(action);
  object_queue_add_tail(&options->actions, &action->super);
}

/* Create a session object. stdout and stderr are shared (although
 * with independent lsh_fd objects). stdin can be used by only one
 * session. */
static struct client_session *
make_client_session(struct lsh_options *options,
		    struct object_list *session_actions)
{
  int in;
  int out;
  int err;
  
  int is_tty = 0;
  struct client_session *session;
  
  struct escape_info *escape = NULL;
#if 0
  struct lsh_callback *detach_cb = NULL;
#endif
  debug("lsh.c: Setting up stdin\n");

  if (options->stdin_file)
    in = open(options->stdin_file, O_RDONLY);
      
  else
    {
      if (options->used_stdin)
	in = open("/dev/null", O_RDONLY);
      else 
	{
	  in = STDIN_FILENO;
	  is_tty = isatty(STDIN_FILENO);
	  
	  options->used_stdin = 1;
	}
    }

  if (in < 0)
    {
      werror("Can't open stdin %e\n", errno);
      return NULL;
    }

  /* Attach the escape char handler, if appropriate. */
  if (options->escape > 0)
    {
      verbose("Enabling explicit escape character `%pc'\n",
	      options->escape);
      escape = make_client_escape(options->escape);
    }
  else if ( (options->escape < 0) && is_tty)
    {
      verbose("Enabling default escape character `%pc'\n",
	      DEFAULT_ESCAPE_CHAR);
      escape = make_client_escape(DEFAULT_ESCAPE_CHAR);
    }
  
  debug("lsh.c: Setting up stdout\n");

  if (options->stdout_file)
    /* FIXME: Use O_TRUNC too? */
    out = open(options->stdout_file, O_WRONLY | O_CREAT, 0666);
  else
    out = STDOUT_FILENO;

  if (out < 0)
    {
      werror("Can't open stdout %e\n", errno);
      close(in);
      return NULL;
    }

  debug("lsh.c: Setting up stderr\n");
  
  if (options->stderr_file)
    /* FIXME: Use O_TRUNC too? */
    err = open(options->stderr_file, O_WRONLY | O_CREAT, 0666);
  else
    err = STDERR_FILENO;

  if (err < 0) 
    {
      werror("Can't open stderr!\n");
      return NULL;
    }

#if 0
  if (options->detach_end) /* Detach? */
    detach_cb = make_detach_callback(options->exit_code);  
#endif

  /* Clear options */
  options->stdin_file = options->stdout_file = options->stderr_file = NULL;

  session = make_client_session_channel(in, out, err,
					session_actions,
					escape,
					WINDOW_SIZE,
					options->exit_code);
  
#if 0
  if (options->detach_end)
    {
      remember_resource(session->resources, make_detach_resource(detach_cb));
      options->detach_end = 0;
    }
#endif

  /* The channel won't get registered in anywhere else until later, so
   * we must register it here to be able to clean up properly if the
   * connection fails early. */
  remember_resource(options->resources, &session->super.super);
  
  return session;
}

static struct client_session_action *
maybe_pty(struct lsh_options *options, int default_pty)
{
#if WITH_PTY_SUPPORT
  int with_pty = options->with_pty;
  if (with_pty < 0)
    with_pty = default_pty;

  if (with_pty && !options->used_pty)
    {
      options->used_pty = 1;
      
358
359
      if (interact_is_tty())
	return &client_request_pty;
360
361
      else
	/* FIXME: Try allocating a remote pty even if we don't have a
362
	   pty locally? I think lsh-1.x and 2.x did that. */
363
364
365
366
367
368
	werror("No tty available.\n");
    }
#endif
  return NULL;
}

369
370
371
372
373
374
375
376
static struct client_session_action *
maybe_x11(struct lsh_options *options)
{  
  if (options->with_x11)
    {
      char *display = getenv(ENV_DISPLAY);
      struct client_session_action *action = NULL;

377
378
      /* FIXME: Make single_connection feature configurable. Should be
	 enabled by default for exec. */
379
      if (display)
380
	action = make_x11_action(display, 0);
381
382
383
384
385
386
387
388
389
390
391

      if (action)
	options->x11_forward = 1;
      else
	werror("Can't find any local X11 display to forward.\n");

      return action;
    }
  return NULL;
}

392
/* Create an interactive session */
393
static struct client_connection_action *
394
395
396
client_shell_session(struct lsh_options *options)
{  
  struct client_session_action *pty = maybe_pty(options, 1);
397
398
  struct client_session_action *x11 = maybe_x11(options);
  struct object_list *session_actions = alloc_object_list(1 + !!pty + !!x11);
399
400
401
402
  unsigned i = 0;

  if (pty)
    LIST(session_actions)[i++] = &pty->super;
403
404
405
  if (x11)
    LIST(session_actions)[i++] = &x11->super;

406
407
408
409
  LIST(session_actions)[i++] = &client_request_shell.super;

  assert(i == LIST_LENGTH(session_actions));
  
410
  return make_open_session_action(
411
412
413
414
	   &make_client_session(options, session_actions)->super);
}

/* Create a session for a subsystem */
415
static struct client_connection_action *
416
417
418
419
420
421
422
client_subsystem_session(struct lsh_options *options,
			 struct lsh_string *subsystem)
{
  struct object_list *session_actions
    = make_object_list(1, make_subsystem_action(subsystem),
		       -1);

423
  return make_open_session_action(
424
425
426
427
	   &make_client_session(options, session_actions)->super);
}

/* Create a session executing a command line */
428
static struct client_connection_action *
429
430
431
432
client_command_session(struct lsh_options *options,
		       struct lsh_string *command)
{
  struct client_session_action *pty = maybe_pty(options, 0);
433
434
  struct client_session_action *x11 = maybe_x11(options);
  struct object_list *session_actions = alloc_object_list(1 + !!pty + !!x11);
435
436
437
438
  unsigned i = 0;

  if (pty)
    LIST(session_actions)[i++] = &pty->super;
439
440
  if (x11)
    LIST(session_actions)[i++] = &x11->super;
441
442
443
444

  LIST(session_actions)[i++] = &make_exec_action(command)->super;

  assert(i == LIST_LENGTH(session_actions));
445
  return make_open_session_action(
446
447
448
	   &make_client_session(options, session_actions)->super);
}

449
450
/* Option parsing */

451
const char *argp_program_version
452
= "lsh (" PACKAGE_STRING "), secsh protocol version " CLIENT_PROTOCOL_VERSION;
453
454
455

const char *argp_program_bug_address = BUG_ADDRESS;

456
457
enum {
  ARG_NOT = 0x400,
458

459
460
461
462
  OPT_PUBLICKEY = 0x201,
  OPT_SLOPPY,
  OPT_STRICT,
  OPT_CAPTURE,
463

464
  OPT_HOST_DB,
Niels Möller's avatar
Niels Möller committed
465

466
467
  OPT_DH,
  OPT_SRP,
468

469
  OPT_HOSTKEY_ALGORITHM,
470
471
472
473
474
475
476
477
478
479
480
481

  OPT_STDIN,
  OPT_STDOUT,
  OPT_STDERR,
 
  OPT_SUBSYSTEM,
  OPT_DETACH,
 
  OPT_ASKPASS,
 
  OPT_WRITE_PID,

482
483
484
485
  OPT_USE_GATEWAY,
  OPT_START_GATEWAY,
  OPT_STOP_GATEWAY
};
486

487
488
489
490
static const struct argp_option
main_options[] =
{
  /* Name, key, arg-name, flags, doc, group */
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
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  { "port", 'p', "PORT", 0, "Connect to this port.", 0 },
  { "user", 'l', "NAME", 0, "Login as this user.", 0 },
  { "askpass", OPT_ASKPASS, "Program", 0,
    "Program to use for reading passwords. "
    "Should be an absolute filename.", 0 },
  { NULL, 0, NULL, 0, "Actions:", 0 },

  { "forward-local-port", 'L', "LOCAL-PORT:TARGET-HOST:TARGET-PORT", 0,
    "Forward TCP/IP connections at a local port", 0 },
  { "forward-socks", 'D', "PORT", OPTION_ARG_OPTIONAL, "Enable socks dynamic forwarding", 0 },
  /* FIXME: Remote forwarding and X11 forwarding doesn't work over a gateway. */
  { "forward-remote-port", 'R', "REMOTE-PORT:TARGET-HOST:TARGET-PORT", 0,
    "Forward TCP/IP connections at a remote port", 0 },
  { "nop", 'N', NULL, 0, "No operation (suppresses the default action, "
    "which is to spawn a remote shell)", 0 },
  { "background", 'B', NULL, 0, "Put process into the background. Implies -N.", 0 },
  { "execute", 'E', "COMMAND", 0, "Execute a command on the remote machine", 0 },
  { "shell", 'S', NULL, 0, "Spawn a remote shell", 0 },
  { "subsystem", OPT_SUBSYSTEM, "SUBSYSTEM-NAME", 0,
#if WITH_PTY_SUPPORT 
    "Connect to given subsystem. Implies --no-pty.",
#else
    "Connect to given subsystem.",
#endif
    0 },

  { NULL, 0, NULL, 0, "Universal not:", 0 },
  { "no", 'n', NULL, 0, "Inverts the effect of the next modifier", 0 },

  { NULL, 0, NULL, 0, "Modifiers that apply to port forwarding:", 0 },
  { "remote-peers", 'g', NULL, 0, "Allow remote access to forwarded ports", 0 },
  { "no-remote-peers", 'g' | ARG_NOT, NULL, 0, 
    "Disallow remote access to forwarded ports (default).", 0 },

  { NULL, 0, NULL, 0, "Modifiers that apply to remote execution:", 0 },
  { "stdin", OPT_STDIN, "Filename", 0, "Redirect stdin", 0},
  { "no-stdin", OPT_STDIN | ARG_NOT, NULL, 0, "Redirect stdin from /dev/null", 0}, 
  { "stdout", OPT_STDOUT, "Filename", 0, "Redirect stdout", 0},
  { "no-stdout", OPT_STDOUT | ARG_NOT, NULL, 0, "Redirect stdout to /dev/null", 0}, 
  { "stderr", OPT_STDERR, "Filename", 0, "Redirect stderr", 0},
  { "no-stderr", OPT_STDERR | ARG_NOT, NULL, 0, "Redirect stderr to /dev/null", 0}, 

  { "detach", OPT_DETACH, NULL, 0, "Detach from terminal at session end.", 0},
  { "no-detach", OPT_DETACH | ARG_NOT, NULL, 0, "Do not detach session at end," 
    " wait for all open channels (default).", 0},

#if WITH_PTY_SUPPORT
  { "pty", 't', NULL, 0, "Request a remote pty (default).", 0 },
  { "no-pty", 't' | ARG_NOT, NULL, 0, "Don't request a remote pty.", 0 },
#endif /* WITH_PTY_SUPPORT */
#if WITH_X11_FORWARD
  { "x11-forward", 'x', NULL, 0, "Enable X11 forwarding.", 0 },
  { "no-x11-forward", 'x' | ARG_NOT, NULL, 0,
    "Disable X11 forwarding (default).", 0 },
#endif

  { NULL, 0, NULL, 0, "Miscellaneous options:", 0 },
  { "escape-char", 'e', "Character", 0, "Escape char. `none' means disable. "
    "Default is to use `~' if we have a tty, otherwise none.", 0 },
  { "write-pid", OPT_WRITE_PID, NULL, 0, "Make -B write the pid of the backgrounded "
    "process to stdout.", 0 },
552

553
  /* Options passed on to lsh-transport. */  
Balázs Scheidler's avatar
Balázs Scheidler committed
554
  { "identity", 'i',  "Identity key", 0, "Use this key to authenticate.", 0 },
555
#if 0
556
557
558
  { "publickey", OPT_PUBLICKEY, NULL, 0,
    "Try publickey user authentication (default).", 0 },
  { "no-publickey", OPT_PUBLICKEY | ARG_NOT, NULL, 0,
559
    "Don't try publickey user authentication.", 0 },
560
#endif
561
  { "host-db", OPT_HOST_DB, "Filename", 0, "By default, ~/.lsh/host-acls", 0},
562
563
564
565
566
567
568
  { "sloppy-host-authentication", OPT_SLOPPY, NULL, 0,
    "Allow untrusted hostkeys.", 0 },
  { "strict-host-authentication", OPT_STRICT, NULL, 0,
    "Never, never, ever trust an unknown hostkey. (default)", 0 },
  { "capture-to", OPT_CAPTURE, "File", 0,
    "When a new hostkey is received, append an ACL expressing trust in the key. "
    "In sloppy mode, the default is ~/.lsh/captured_keys.", 0 },
569
#if 0
570
571
572
573
574
#if WITH_SRP
  { "srp-keyexchange", OPT_SRP, NULL, 0, "Enable experimental SRP support.", 0 },
  { "no-srp-keyexchange", OPT_SRP | ARG_NOT, NULL, 0, "Disable experimental SRP support (default).", 0 },
#endif /* WITH_SRP */

575
576
577
  { "dh-keyexchange", OPT_DH, NULL, 0,
    "Enable DH support (default, unless SRP is being used).", 0 },

578
  { "no-dh-keyexchange", OPT_DH | ARG_NOT, NULL, 0, "Disable DH support.", 0 },
Niels Möller's avatar
Niels Möller committed
579
#endif
580

581
582
583
584
585
  { "crypto", 'c', "ALGORITHM", 0, "", 0 },
  { "compression", 'z', "ALGORITHM", OPTION_ARG_OPTIONAL,
    "Enable compression. Default algorithm is zlib.", 0 },
  { "mac", 'm', "ALGORITHM", 0, "Select MAC algorithm", 0 },
  { "hostkey-algorithm", OPT_HOSTKEY_ALGORITHM, "ALGORITHM", 0,
586
    "Select host authentication algorithm.", 0 },
587
588
589
590
591
592
593
594
595
596
597
598
599

  /* Gateway options */
  { "use-gateway", OPT_USE_GATEWAY, NULL, 0,
    "Always use a local gateway", 0 },
  { "no-use-gateway", OPT_USE_GATEWAY | ARG_NOT, NULL, 0,
    "Never use a local gateway", 0 },
  { "gateway", 'G', NULL, 0,
    "Use any existing gateway; if none exists, start a new one.", 0 },
  { "start-gateway", OPT_START_GATEWAY, NULL, 0,
    "Stop any existing gateway, and start a new one.", 0 },
  { "stop-gateway", OPT_START_GATEWAY, NULL, 0,
    "Stop any existing gateway. Disables all other actions.", 0 },

600
601
602
603
604
605
606
  { NULL, 0, NULL, 0, NULL, 0 }
};


static const struct argp_child
main_argp_children[] =
{
607
  { &werror_argp, 0, "", 0 },
608
609
610
  { NULL, 0, NULL, 0}
};

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
static int
parse_arg_unsigned(const char *arg, unsigned long *n)
{
  char *end;
  if (*arg == 0)
    return 0;

  *n = strtoul(arg, &end, 0);
  return *end == 0;
}

/* Parse the argument for -R and -L */
static int
parse_forward_arg(char *arg,
		  unsigned long *listen_port,
		  struct address_info **target)
{
  const char *host;
  const char *target_port;
  char *sep;
  
  sep = strchr(arg, ':');
  if (!sep)
    return 0;

  sep[0] = '\0';

  if (!parse_arg_unsigned(arg, listen_port))
    return 0;
  
  host = sep + 1;

  sep = strchr(host, ':');
  if (!sep)
    return 0;

  sep[0] = '\0';
  target_port = sep + 1;

  *target = io_lookup_address(host, target_port);
  
  return *target != NULL;
}

655
656
#define CASE_ARG(opt, attr, none)		\
  case opt:					\
657
    if (self->not)				\
658
      {						\
659
        self->not = 0;				\
660
661
662
663
664
665
666
667
668
						\
      case opt | ARG_NOT:			\
        self->attr = none;			\
        break;					\
      }						\
      						\
    self->attr = arg;				\
    break

669
670
#define CASE_FLAG(opt, flag)			\
  case opt:					\
671
    if (self->not)				\
672
      {						\
673
        self->not = 0;				\
674
675
676
677
678
679
						\
      case opt | ARG_NOT:			\
        self->flag = 0;				\
        break;					\
      }						\
      						\
680
681
    self->flag = 1;				\
    break
682

683
684
685
686
687
688
689
690
691
692
static error_t
main_argp_parser(int key, char *arg, struct argp_state *state)
{
  CAST(lsh_options, self, state->input);
  
  switch(key)
    {
    default:
      return ARGP_ERR_UNKNOWN;
    case ARGP_KEY_INIT:
693
      state->child_inputs[0] = &self->super;
694
      break;
695
      
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
    case ARGP_KEY_NO_ARGS:
      argp_usage(state);
      break;
    case ARGP_KEY_ARG:
      if (!state->arg_num)
	self->target = arg;
      
      else
	/* Let the next case parse it.  */
	return ARGP_ERR_UNKNOWN;

      break;
    case ARGP_KEY_ARGS:
      add_action
	(self,
	 client_command_session
	 (self, client_rebuild_command_line(state->argc - state->next,
					    state->argv + state->next)));
      self->start_shell = 0;
      break;
716
    case ARGP_KEY_END:
717
      if (self->inhibit_actions)
718
719
	break;

720
721
722
      if (!werror_init(&self->super))
	argp_failure(state, EXIT_FAILURE, errno, "Failed to open log file");

723
724
725
726
727
      if (!self->home)
	{
	  argp_error(state, "No home directory. Please set HOME in the environment.");
	  break;
	}
728
729
730
731
732
733
734

      if (self->start_gateway > 0 && self->use_gateway > 0)
	{
	  argp_error(state, "--start-gateway and --use-gateway are "
		     "mutually exclusive.");
	  break;
	}
735
736
      /* We can't add the gateway action immediately when the -G
       * option is encountered, as we need the name and port of the
737
       * remote machine (self->remote) first.
738
739
740
       */

      if (self->start_gateway || self->stop_gateway || self->use_gateway)
741
	{
742
	  if (!self->local_user)
743
	    {
744
745
746
	      argp_error(state, "You have to set LOGNAME in the environment, "
			 " if you want to use the gateway feature.");
	      break;
747
	    }
748
749
750
	  self->gateway = make_gateway_address(self->local_user,
					       self->user,
					       self->target);
751

752
	  if (!self->gateway)
753
	    {
754
755
	      argp_error(state, "Local or remote user name, or the target host name, are too "
			 "strange for the gateway socket name construction.");
756
	      break;
757
	    }
758
	}
759

760
761
762
763
764
      /* Add shell action */
      if (self->start_shell)
	add_action(self, client_shell_session(self));

      if (object_queue_is_empty(&self->actions) && !self->stop_gateway)
765
766
767
768
	{
	  argp_error(state, "No actions given.");
	  break;
	}
769

770
771
772
773
774
775
776
777
778
779
780
      break;

    case 'p':
      self->port = arg;
      break;

    case 'l':
      self->user = arg;
      break;

    case OPT_ASKPASS:
781
782
783
784
      arglist_push(&self->transport_args, "--askpass");
      arglist_push(&self->transport_args, arg);
      
      interact_set_askpass(arg);      
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
      break;
      
    case 'e':
      if (arg[0] && !arg[1])
	/* A single char argument */
	self->escape = arg[0];
      else if (!strcasecmp(arg, "none"))
	self->escape = 0;
      else
	argp_error(state, "Invalid escape char: `%s'. "
		   "You must use a single character or `none'.", arg);
      break;

    case 'E':
      add_action(self,
		 client_command_session(self,
					ssh_format("%lz", arg)));
      break;

    case 'S':
      add_action(self, client_shell_session(self));
      break;

    case OPT_SUBSYSTEM:
      add_action(self, client_subsystem_session(self,
						ssh_format("%lz", arg)));

      self->start_shell = 0;
#if WITH_PTY_SUPPORT
      self->with_pty = 0;
#endif
      break;

    case 'L':
      {
	unsigned long listen_port;
	struct address_info *target;

	if (!parse_forward_arg(arg, &listen_port, &target))
	  argp_error(state, "Invalid forward specification `%s'.", arg);

	add_action(self, forward_local_port
		   (make_address_info((self->with_remote_peers
				       ? NULL
				       : ssh_format("%lz", "127.0.0.1")),
				      listen_port),
		    target));
	break;
      }      

    case 'D':
      {
	unsigned long socks_port = DEFAULT_SOCKS_PORT;
	if (arg && (parse_arg_unsigned(arg, &socks_port) == 0 || socks_port > 0xffff))
	  argp_error(state, "Invalid port number `%s' for socks.", arg);

	add_action(self, make_socks_server
		   (make_address_info((self->with_remote_peers
				       ? NULL
				       : ssh_format("%lz", "127.0.0.1")),
				      socks_port)));
	break;
      }

    case 'N':
      self->start_shell = 0;
      break;

    case 'B':
      self->start_shell = 0;
855
      self->detach = 1;
856
      break;
857

858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
      /* FIXME: Doesn't yet work over the gateway. */
    case 'R':
      {
	unsigned long listen_port;
	struct address_info *target;

	if (!parse_forward_arg(arg, &listen_port, &target))
	  argp_error(state, "Invalid forward specification '%s'.", arg);

	add_action(self, forward_remote_port
		   (make_address_info((self->with_remote_peers
				       ? ssh_format("%lz", "0.0.0.0")
				       : ssh_format("%lz", "127.0.0.1")),
				      listen_port),
		    target));

	self->remote_forward = 1;
	break;
      }

    CASE_FLAG('g', with_remote_peers);

#if WITH_PTY_SUPPORT
    CASE_FLAG('t', with_pty);
#endif /* WITH_PTY_SUPPORT */

#if WITH_X11_FORWARD
    CASE_FLAG('x', with_x11);
#endif

    CASE_FLAG(OPT_DETACH, detach_end);
    CASE_FLAG(OPT_WRITE_PID, write_pid);
    
    CASE_ARG(OPT_STDIN, stdin_file, "/dev/null");
    CASE_ARG(OPT_STDOUT, stdout_file, "/dev/null"); 
    CASE_ARG(OPT_STDERR, stderr_file, "/dev/null");

Balázs Scheidler's avatar
Balázs Scheidler committed
895
    case 'i':
896
897
      arglist_push(&self->transport_args, "--identity");
      arglist_push(&self->transport_args, arg);
898
      break;
Niels Möller's avatar
Niels Möller committed
899

900
901
#if 0
      CASE_FLAG(OPT_PUBLICKEY, with_publickey);
902
#endif
Niels Möller's avatar
Niels Möller committed
903
    case OPT_HOST_DB:
904
905
      arglist_push(&self->transport_args, "--host-db");
      arglist_push(&self->transport_args, arg);
Niels Möller's avatar
Niels Möller committed
906
      break;
907

908
    case OPT_SLOPPY:
909
      arglist_push(&self->transport_args, "--sloppy-host-authentication");
910
911
912
      break;

    case OPT_STRICT:
913
      arglist_push(&self->transport_args, "--strict-host-authentication");
914
915
916
      break;

    case OPT_CAPTURE:
917
918
919
920
921
922
923
      arglist_push(&self->transport_args, "--capture-to");
      arglist_push(&self->transport_args, arg);
      break;

    case 'c':
      arglist_push(&self->transport_args, "-c");
      arglist_push(&self->transport_args, arg);
924
      break;
925

926
927
928
929
930
931
932
933
934
935
936
937
938
    case 'm':
      arglist_push(&self->transport_args, "-m");
      arglist_push(&self->transport_args, arg);
      break;

    case 'z':
      if (!arg)
	arglist_push(&self->transport_args, "-z");
      else
	arglist_push_optarg(&self->transport_args, "-z", arg);
      break;

#if 0
939
940
    CASE_FLAG(OPT_DH, with_dh_keyexchange);
    CASE_FLAG(OPT_SRP, with_srp_keyexchange);
Niels Möller's avatar
Niels Möller committed
941
#endif
942

943
944
945
    CASE_FLAG(OPT_USE_GATEWAY, use_gateway);

    case 'G':
946
947
948
      /* FIXME: It would be desirable to have this option also imply
	 that lsh is backgrounded when the primary actions are
	 completed. */
949
950
951
952
953
954
955
956
957
958
959
960
      self->start_gateway = -1;
      break;

    case OPT_START_GATEWAY:
      self->start_gateway = 1;
      self->stop_gateway = 1;
      break;

    case OPT_STOP_GATEWAY:
      self->stop_gateway = 1;
      break;

961
962
963
964
    case 'n':
      self->not = !self->not;
      break;

965
    }
966

967
968
969
970
971
972
973
974
975
976
977
978
979
980
  return 0;
}

static const struct argp
main_argp =
{ main_options, main_argp_parser, 
  ( "host\n"
    "host command ..."), 
  ( "Connects to a remote machine\v"
    "Connects to the remote machine, and then performs one or more actions, "
    "i.e. command execution, various forwarding services. The default "
    "action is to start a remote interactive shell or execute a given "
    "command on the remote machine." ),
  main_argp_children,
981
  NULL, NULL
982
983
};

Niels Möller's avatar
Niels Möller committed
984
985
986
987
988
989
static void
transport_exit_callback(struct exit_callback *s UNUSED,
			int signalled, int core, int value)
{
  if (signalled)
    {
990
      werror("Transport process killed by %s (signal %i)%s.\n",
Niels Möller's avatar
Niels Möller committed
991
992
993
994
995
996
	     STRSIGNAL(value), value, core ? " (core dumped)" : "");
      /* FIXME: Design and document error code values. */
      exit(17);
    }
  else if (value)
    {
997
      werror("Transport process exited with error code %i.\n", value);
Niels Möller's avatar
Niels Möller committed
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
      exit(value);
    }
  else
    {
      werror("Transport process exited successfully.\n");
      /* Do nothing */
    }
}

static struct exit_callback *
make_transport_exit_callback(void)
{
  NEW(exit_callback, self);
  self->exit = transport_exit_callback;
  return self;
}

1015
static int
1016
fork_lsh_transport(struct lsh_options *options)
Niels Möller's avatar
Niels Möller committed
1017
1018
1019
1020
1021
1022
1023
{
  int pipe[2];
  pid_t child;

  if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe) < 0)
    {
      werror("fork_lsh_transport: socketpair failed: %e\n", errno);
1024
      return -1;
Niels Möller's avatar
Niels Möller committed
1025
1026
1027
1028
1029
1030
1031
    }
  child = fork();
  if (child < 0)
    {
      werror("fork_lsh_transport: fork failed: %e\n", errno);
      close(pipe[0]);
      close(pipe[1]);
1032
      return -1;
Niels Möller's avatar
Niels Möller committed
1033
1034
1035
1036
1037
1038
    }
  else if (child)
    {
      /* Parent process */
      close(pipe[1]);
      
1039
      reaper_handle(child, make_transport_exit_callback());
1040
      return pipe[0];
Niels Möller's avatar
Niels Möller committed
1041
1042
1043
1044
    }
  else
    {
      /* Child process */
1045
      char **argv;
1046
1047
      int error_fd = get_error_stream();

Niels Möller's avatar
Niels Möller committed
1048
1049
1050
1051
1052
      close(pipe[0]);
      dup2(pipe[1], STDIN_FILENO);
      dup2(pipe[1], STDOUT_FILENO);
      close(pipe[1]);

1053
1054
1055
1056
      if (error_fd >= 0 && error_fd != STDERR_FILENO)
	dup2(error_fd, STDERR_FILENO);

      arglist_push(&options->transport_args, "-l");
1057
      arglist_push(&options->transport_args, options->user);
1058

1059
      if (options->super.verbose > 0)
1060
	arglist_push(&options->transport_args, "-v");
1061
      if (options->super.quiet > 0)
1062
	arglist_push(&options->transport_args, "-q");	
1063
      if (options->super.debug > 0)
1064
	arglist_push(&options->transport_args, "--debug");
1065
      if (options->super.trace > 0)
1066
	arglist_push(&options->transport_args, "--trace");
1067

1068
      if (options->port)
1069
1070
	{
	  arglist_push(&options->transport_args, "-p");
1071
	  arglist_push(&options->transport_args, options->port);
1072
	}
1073
      arglist_push(&options->transport_args, options->target);
1074
1075
      
      argv = (char **) options->transport_args.argv;
1076

1077
1078
      verbose("Starting %z.\n", argv[0]);
      execv(argv[0], argv);
Niels Möller's avatar
Niels Möller committed
1079
1080
1081
1082
1083
      werror("fork_lsh_transport: exec failed: %e\n", errno);
      _exit(EXIT_FAILURE);
    }
}

1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
static int
process_hello_message(int fd)
{
  struct lsh_string *buf = lsh_string_alloc(LSH_HELLO_LINE_LENGTH);
  static const char expected[] = "LSH " STRINGIZE(LSH_HELLO_VERSION) " OK";

  int res = lsh_string_read (buf, 0, fd, LSH_HELLO_LINE_LENGTH);
  if (res < 0)
    {
      werror ("Reading local hello message failed: %e\n", errno);
    fail:
      lsh_string_free (buf);
      return 0;
    }
  if (!res)
    {
      werror ("Lost local connection.\n");
      goto fail;
    }
  if (res != LSH_HELLO_LINE_LENGTH)
    {
      werror ("Truncated hello message.\n");
      goto fail;
    }

  if (memcmp (lsh_string_data (buf), expected, sizeof(expected) - 1) != 0)
    {
      werror ("Invalid hello message.\n");
      goto fail;
    }
  lsh_string_free (buf);
  return 1;  
}

Niels Möller's avatar
Niels Möller committed
1118
int
1119
main(int argc, char **argv)
Niels Möller's avatar
Niels Möller committed
1120
{
1121
  struct client_connection *connection;
1122
  struct lsh_options *options;
1123
  int fd;
1124
  
1125
1126
  /* Default exit code if something goes wrong. */
  int lsh_exit_code = 17;
1127

1128
1129
1130
  if (!unix_interact_init(1))
    return EXIT_FAILURE;

1131
  io_init();
Niels Möller's avatar
Niels Möller committed
1132
  reaper_init();
1133
  
Niels Möller's avatar
Niels Möller committed
1134
1135
1136
  /* For filtering messages. Could perhaps also be used when converting
   * strings to and from UTF8. */
  setlocale(LC_CTYPE, "");
1137

1138
1139
  /* FIXME: Choose character set depending on the locale */
  set_local_charset(CHARSET_LATIN1);
1140

1141
  options = make_options(&lsh_exit_code);
1142

1143
1144
  if (!options)
    return EXIT_FAILURE;
1145

1146
  options->inhibit_actions = 1; /* Disable normal actions performed at end */
1147
  env_parse(&main_argp, getenv(ENV_LSHFLAGS), ARGP_IN_ORDER, options);
1148
1149
  options->inhibit_actions = 0; /* Reenable */

1150
  argp_parse(&main_argp, argc, argv, ARGP_IN_ORDER, NULL, options);
1151

1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
  if (options->stop_gateway)
    {
      /* Stop any existing gateway. */
      static const uint8_t stop_message[] =
	{
	  0, 0, 0, 0,  /* seqno */
	  0, 0, 0, 1,  /* length */
	  SSH_LSH_GATEWAY_STOP,
	};

      fd = io_connect_local(options->gateway);
      if (fd < 0)
	{
	  werror("Could not open gateway: %e\n", errno);
	  if (!options->start_gateway)
	    return EXIT_FAILURE;
	}
      else
	{
	  if (!write_raw(fd, sizeof(stop_message), stop_message))
	    {
	      werror("Failed to send stop message to gateway.\n");
	      if (!options->start_gateway)
		return EXIT_FAILURE;
	    }
	  close(fd);
	}
      if (!options->start_gateway)
	return EXIT_SUCCESS;
    }

  fd = -1;

  if (options->use_gateway)
    {
      fd = io_connect_local(options->gateway);
      if (fd < 0)
	{
	  werror("Could not open gateway: %e\n", errno);
	  if (options->start_gateway < 0)
	    options->start_gateway = 1;
	}
    }

  if (fd < 0 && options->use_gateway != 1)
1197
1198
1199
1200
1201
1202
1203
1204
    fd = fork_lsh_transport(options);

  if (fd < 0)
    return EXIT_FAILURE;

  if (!process_hello_message (fd))
    return EXIT_FAILURE;

1205
  connection = make_client_connection(fd);
1206
1207
1208
  gc_global(&connection->super.super);

  if (options->start_gateway == 1)
1209
1210
1211
    {
      struct resource *port = make_gateway_port(options->gateway, connection);
      if (port)
1212
1213
1214
	{
	  remember_resource(connection->super.resources, port);
	  options->x11_forward = 1;
1215
	  options->remote_forward = 1;
1216
	}
1217
1218
1219
1220
      else
	werror("Failed to setup gateway.\n");
    }
  
1221
1222
  /* Contains session channels to be opened. */
  remember_resource(connection->super.resources,
1223
		    &options->resources->super);
1224

1225
#if WITH_TCP_FORWARD
1226
  if (options->remote_forward)
1227
1228
    ALIST_SET(connection->super.channel_types, ATOM_FORWARDED_TCPIP,
	      &channel_open_forwarded_tcpip.super);
1229
#endif
1230

1231
1232
1233
1234
1235
#if WITH_X11_FORWARD
  if (options->x11_forward)
    ALIST_SET(connection->super.channel_types, ATOM_X11,
	      &channel_open_x11.super);
#endif
1236

1237
  while (!object_queue_is_empty(&options->actions))
1238
    {
1239
      CAST_SUBTYPE(client_connection_action, action,
1240
		   object_queue_remove_head(&options->actions));
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
      action->action(action, &connection->super);
    }

  if (options->detach)
    {
      pid_t pid = fork();
      switch (pid)
	{
	case 0:
	  /* Child. Just go on. */
	  /* FIXME: Should we create a new process group, close our tty
	   * and stdio, etc? */	 
	  break;
	case -1:
	  /* Error */
	  werror("fork failed when detaching: %e\n", errno);
	  break;
	default:
	  /* Parent */
	  if (options->write_pid)
	    {
	      struct lsh_string *msg = ssh_format("%di\n", pid);
	      if (!write_raw (STDOUT_FILENO, STRING_LD(msg)))
		werror ("Write to stdout failed!?: %e\n", errno);
	    }
	  _exit(EXIT_SUCCESS);
	}
1268
    }
1269
  io_run();
1270
  
1271
1272
  /* FIXME: Perhaps we have to reset the stdio file descriptors to
   * blocking mode? */
Niels Möller's avatar
Niels Möller committed
1273
  return lsh_exit_code;
Niels Möller's avatar
Niels Möller committed
1274
}