lsh.c 25.6 KB
Newer Older
Niels Möller's avatar
Niels Möller committed
1
2
3
/* lsh.c
 *
 * client main program
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * $Id$ */

/* lsh, an implementation of the ssh protocol
 *
 * Copyright (C) 1998 Niels Mller
 *
 * 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
#include "algorithms.h"
Niels Möller's avatar
Niels Möller committed
27
28
#include "alist.h"
#include "atoms.h"
29
#include "channel.h"
30
#include "charset.h"
Niels Möller's avatar
Niels Möller committed
31
#include "client.h"
Niels Möller's avatar
Niels Möller committed
32
#include "client_keyexchange.h"
33
#include "client_pty.h"
34
#include "client_userauth.h"
35
36
#include "compress.h"
#include "connection_commands.h"
Niels Möller's avatar
Niels Möller committed
37
#include "crypto.h"
Niels Möller's avatar
Niels Möller committed
38
#include "format.h"
39
#include "interact.h"
Niels Möller's avatar
Niels Möller committed
40
#include "io.h"
41
#include "io_commands.h"
42
#include "lookup_verifier.h"
Niels Möller's avatar
Niels Möller committed
43
#include "randomness.h"
Niels Möller's avatar
Niels Möller committed
44
#include "service.h"
45
#include "sexp.h"
46
#include "spki_commands.h"
47
#include "ssh.h"
48
#include "tcpforward_commands.h"
49
#include "tty.h"
Niels Möller's avatar
Niels Möller committed
50
#include "werror.h"
Niels Möller's avatar
Niels Möller committed
51
#include "xalloc.h"
Niels Möller's avatar
Niels Möller committed
52

53
#include <assert.h>
54
55
56
57
58
59
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

60
#if HAVE_UNISTD_H
61
#include <unistd.h>
62
#endif
63

64
65
66
67
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

68
#include "lsh_argp.h"
69

70
/* Forward declarations */
71
72
73
74
75
76
77
78
79
80
struct command_simple options2remote;
#define OPTIONS2REMOTE (&options2remote.super.super)

struct command_simple lsh_verifier_command;
#define OPTIONS2VERIFIER (&lsh_verifier_command.super.super)

struct command_simple lsh_login_command;
#define LSH_LOGIN (&lsh_login_command.super.super)

static struct command options2known_hosts;
81
#define OPTIONS2KNOWN_HOSTS (&options2known_hosts.super)
82
83
84
85

static struct command options2identities;
#define OPTIONS2IDENTITIES (&options2identities.super)
		    
86
87
#include "lsh.c.x"

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

91
92
93
/* Window size for the session channel */
#define WINDOW_SIZE (SSH_MAX_PACKET << 3)

94
95
96
97
98
99
/* GABA:
   (class
     (name lsh_options)
     (super algorithms_options)
     (vars
       (backend object io_backend)
Niels Möller's avatar
Niels Möller committed
100

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
130
131
132
133
134
       (signature_algorithms object alist)
       (home . "const char *")
       
       ; For i/o exceptions 
       (handler object exception_handler)

       (exit_code . "int *")
       
       (not . int)
       (port . "char *")
       (remote object address_info)

       (user . "char *")
       (identity . "char *")
       (publickey . int)

       (sloppy . int)
       (capture . "const char *")
       (capture_file object abstract_write)

       (known_hosts . "const char *")
       
       ; -1 means default behaviour
       (with_pty . int)

       (with_remote_peers . int)
       
       (start_shell . int)
       (remote_forward . int)
       (actions struct object_queue)))
*/


static struct lsh_options *
Niels Möller's avatar
Niels Möller committed
135
136
make_options(struct alist *algorithms,
	     struct io_backend *backend,
137
138
139
140
141
142
143
144
145
146
147
148
	     struct randomness *random,
	     struct exception_handler *handler,
	     int *exit_code)
{
  NEW(lsh_options, self);

  init_algorithms_options(&self->super, algorithms);
  
  self->backend = backend;

  self->home = getenv("HOME");
  
149
150
151
  self->signature_algorithms
    = make_alist(1,
		 ATOM_DSA, make_dsa_algorithm(random), -1);
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  
  self->handler = handler;
  self->exit_code = exit_code;
  
  self->not = 0;
  self->remote = NULL;
  self->user = getenv("LOGNAME");
  self->port = "ssh";

  self->sloppy = 0;
  self->capture = NULL;
  self->capture_file = NULL;

  self->known_hosts = NULL;
  /* self->known_hosts_file = NULL; */
  
  self->with_pty = -1;
  self->start_shell = 1;
  self->with_remote_peers = 0;
  object_queue_init(&self->actions);

  self->publickey = 1;
  
  return self;
}

Niels Möller's avatar
Niels Möller committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238

/* Host to connect to */
COMMAND_SIMPLE(options2remote)
{
  CAST(lsh_options, options, a);
  return &options->remote->super;
}

/* Open hostkey database. By default, "~/.lsh/known_hosts". */

static void
do_options2known_hosts(struct command *ignored UNUSED,
		       struct lsh_object *a,
		       struct command_continuation *c,
		       struct exception_handler *e)
{
  CAST(lsh_options, options, a);
  
  struct lsh_string *tmp = NULL;
  const char *s = NULL;
  struct io_fd *f;
  
  if (options->known_hosts)
    s = options->known_hosts;
  else 
    {
      tmp = ssh_format("%lz/.lsh/known_hosts%c", options->home, 0);
      s = tmp->data;
    }
  
  f = io_read_file(options->backend, s, e);

  if (!f)
    {
      werror("Failed to open '%z' (errno = %i): %z.\n",
	     s, errno, STRERROR(errno));
      COMMAND_RETURN(c, make_spki_context(options->signature_algorithms));
    }
  else
    {
      CAST_SUBTYPE(command, read,
		   make_spki_read_acls(options->signature_algorithms));
      COMMAND_CALL(read, f, c, e);
    }
  lsh_string_free(tmp);
}

static struct command options2known_hosts =
STATIC_COMMAND(do_options2known_hosts);

/* Read user's private keys. By default, "~/.lsh/identity". */
static void
do_options2identities(struct command *ignored UNUSED,
		      struct lsh_object *a,
		      struct command_continuation *c,
		      struct exception_handler *e)
{
  CAST(lsh_options, options, a);
  
  struct lsh_string *tmp = NULL;
  const char *s = NULL;
239
  struct io_fd *f = NULL;
Niels Möller's avatar
Niels Möller committed
240

241
242
243
  trace("do_options2identities\n");
  
  if (!options->publickey)
Niels Möller's avatar
Niels Möller committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    {
      COMMAND_RETURN(c, make_object_list(0, -1));
      return;
    }
  if (options->identity)
    s = options->identity;
  else 
    {
      tmp = ssh_format("%lz/.lsh/identity%c", options->home, 0);
      s = tmp->data;
    }
  
  f = io_read_file(options->backend, s, e);
  
  if (!f)
    {
      werror("Failed to open '%z' (errno = %i): %z.\n",
	     s, errno, STRERROR(errno));
      COMMAND_RETURN(c, make_object_list(0, -1));
    }
  else
265
266
    COMMAND_CALL(make_spki_read_userkeys(options->signature_algorithms),
		 f, c, e);
Niels Möller's avatar
Niels Möller committed
267
268
269
270
271
272
273
  
  lsh_string_free(tmp);
}

static struct command options2identities =
STATIC_COMMAND(do_options2identities);

274
275
276
277
278
279
280
281
/* GABA:
   (class
     (name options_command)
     (super command)
     (vars
       (options object lsh_options)))
*/

Niels Möller's avatar
Niels Möller committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
static struct command *
make_options_command(struct lsh_options *options,
		     void (*call)(struct command *s,
				  struct lsh_object *a,
				  struct command_continuation *c,
				  struct exception_handler *e))
{
  NEW(options_command, self);
  self->super.call = call;
  self->options = options;

  return &self->super;
}

/* Maps a host key to a (trusted) verifier object.
 *
 * NOTE: Handles only ssh-dss keys. */
Niels Möller's avatar
Niels Möller committed
299

300
/* GABA:
301
   (class
302
     (name lsh_host_db)
303
304
     (super lookup_verifier)
     (vars
305
306
307
308
309
310
311
       (db object spki_context)
       (access object sexp)
       (host object address_info)
       ; Allow unauthorized keys
       (sloppy . int)
       ; If non-null, append an ACL for the received key to this file.
       (file object abstract_write)
312
       (hash object hash_algorithm) ; For fingerprinting
313
314
       ;; (algorithm object signature_algorithm)
       ))
315
316
*/

317
static struct verifier *
318
do_lsh_lookup(struct lookup_verifier *c,
Niels Möller's avatar
Niels Möller committed
319
320
321
	      int method,
	      struct lsh_string *keyholder UNUSED,
	      struct lsh_string *key)
Niels Möller's avatar
Niels Möller committed
322
{
323
  CAST(lsh_host_db, self, c);
324
  struct spki_subject *subject;
Niels Möller's avatar
Niels Möller committed
325

326
327
328
329
330
331
332
  switch (method)
    {
    case ATOM_SSH_DSS:
      {
	struct dsa_verifier *v = make_ssh_dss_verifier(key->length, key->data);
	if (!v)
	  {
333
	    werror("do_lsh_lookup: Invalid ssh-dss key.\n");
334
335
336
337
338
339
340
341
342
343
344
345
346
347
	    return NULL;
	  }
	subject = SPKI_LOOKUP(self->db,
			      dsa_to_spki_public_key(&v->public),
			      &v->super);
	assert(subject);
	assert(subject->verifier);
	break;
      }
    case ATOM_SPKI:
      {
	struct sexp *e = string_to_sexp(key, 0);
	if (!e)
	  {
348
	    werror("do_lsh_lookup: Invalid spki s-expression.\n");
349
350
351
352
353
354
	    return NULL;
	  }
	  
	subject = SPKI_LOOKUP(self->db, e, NULL);
	if (!subject)
	  {
355
	    werror("do_lsh_lookup: Invalid spki key.\n");
356
357
358
359
	    return NULL;
	  }
	if (!subject->verifier)
	  {
360
	    werror("do_lsh_lookup: Valid SPKI subject, but no key available.\n");
361
362
363
364
365
	    return NULL;
	  }
	break;
      }
    default:
366
      werror("do_lsh_lookup: Unknown key type. Should not happen!\n");
367
368
369
370
      return NULL;
    }

  assert(subject->key);
371
  
372
373
374
375
  /* Check authorization */

  if (SPKI_AUTHORIZE(self->db, subject, self->access))
    {
376
      verbose("SPKI host authorization successful!\n");
377
378
    }
  else
379
    {
380
      verbose("SPKI authorization failed.\n");
381
      if (!self->sloppy)
382
383
384
385
	{
	  werror("lsh: Server's hostkey is not trusted. Disconnecting.\n");
	  return NULL;
	}
386
      
387
388
      /* Ok, let's see if we want to use this untrusted key. */
      if (!quiet_flag)
389
	{
390
391
392
393
394
395
	  /* Display fingerprint */
	  struct lsh_string *fingerprint
	    = hash_string(self->hash,
			  sexp_format(subject->key, SEXP_CANONICAL, 0),
			  1);
			  
396
	  if (!yes_or_no(ssh_format("Received unauthenticated key for host %lS\n"
397
398
399
400
				    "Fingerprint: %lfxS\n"
				    "Do you trust this key? (y/n) ",
				    self->host->ip, fingerprint), 0, 1))
	    return NULL;
401
402
	}
      
Niels Möller's avatar
Niels Möller committed
403
      /* Write an ACL to disk. */
404
      if (self->file)
405
	{
Niels Möller's avatar
Niels Möller committed
406
407
408
409
410
411
412
413
414
415
416
417
	  A_WRITE(self->file, ssh_format("\n; ACL for host %lS\n", self->host->ip));
	  A_WRITE(self->file,
		  sexp_format(sexp_l(2, sexp_a(ATOM_ACL),
				     sexp_l(3, sexp_a(ATOM_ENTRY),
					    subject->key,
					    sexp_l(2, sexp_a(ATOM_TAG),
						   self->access,
						   -1),
					    -1),
				     -1),
			      SEXP_TRANSPORT, 0));
	  A_WRITE(self->file, ssh_format("\n"));
418
419
420
	}
    }
  
421
  return subject->verifier;
Niels Möller's avatar
Niels Möller committed
422
423
}

424
static struct lookup_verifier *
425
make_lsh_host_db(struct spki_context *db,
Niels Möller's avatar
Niels Möller committed
426
427
428
		 struct address_info *host,
		 int sloppy,
		 struct abstract_write *file)
Niels Möller's avatar
Niels Möller committed
429
{
430
  NEW(lsh_host_db, res);
431

432
  res->super.lookup = do_lsh_lookup;
433
434
435
436
437
  res->db = db;
  res->access = make_ssh_hostkey_tag(host);
  res->host = host;
  res->sloppy = sloppy;
  res->file = file;
438
  res->hash = &sha1_algorithm;
Niels Möller's avatar
Niels Möller committed
439
440
441
442

  return &res->super;
}

Niels Möller's avatar
Niels Möller committed
443
/* Takes an spki_context as argument and returns a lookup_verifier */
444
445
446
447
448
449
450
451
452
static void
do_lsh_verifier(struct command *s,
		struct lsh_object *a,
		struct command_continuation *c,
		struct exception_handler *e UNUSED)
{
  CAST(options_command, self, s);
  CAST_SUBTYPE(spki_context, db, a);
  COMMAND_RETURN(c, make_lsh_host_db(db,
Niels Möller's avatar
Niels Möller committed
453
454
455
				     self->options->remote,
				     self->options->sloppy,
				     self->options->capture_file));
456
457
}

Niels Möller's avatar
Niels Möller committed
458
459
/* Takes an options object as argument and returns a lookup_verifier */

460
461
462
463
COMMAND_SIMPLE(lsh_verifier_command)
{
  CAST(lsh_options, options, a);

Niels Möller's avatar
Niels Möller committed
464
465
466
  return
    & make_options_command(options,
			   do_lsh_verifier)->super;
467
468
}

Niels Möller's avatar
Niels Möller committed
469
/* list-of-public-keys -> login-command */
470
static void do_lsh_login(struct command *s,
Niels Möller's avatar
Niels Möller committed
471
472
473
			 struct lsh_object *a,
			 struct command_continuation *c,
			 struct exception_handler *e UNUSED)
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
{
  CAST(options_command, self, s);
  CAST_SUBTYPE(object_list, keys, a);

  COMMAND_RETURN(c,
		 make_client_userauth(ssh_format("%lz", self->options->user),
				      ATOM_SSH_CONNECTION,
				      (LIST_LENGTH(keys)
				       ? make_object_list
				         (2, 
					  make_client_publickey_auth(keys),
					  make_client_password_auth(), 
					  -1)
				       : make_object_list(1, make_client_password_auth(), 
							  -1))));
}

Niels Möller's avatar
Niels Möller committed
491
/* (login options public-keys connection) */
492
493
494
495
COMMAND_SIMPLE(lsh_login_command)
{
  CAST(lsh_options, options, a);

Niels Möller's avatar
Niels Möller committed
496
497
498
  return
    & make_options_command(options,
			   do_lsh_login)->super;
499
500
}

Niels Möller's avatar
Niels Möller committed
501
502
/* GABA:
   (expr
503
     (name make_lsh_connect)
504
505
     (params
       (connect object command)
506
       (handshake object handshake_info)
Niels Möller's avatar
Niels Möller committed
507
508
       (userauth_service object command)
       (requests object object_list))
509
     (expr (lambda (options)
Niels Möller's avatar
Niels Möller committed
510
511
512
513
514
515
516
517
              ; What to do with the service
	     ((progn requests)
	       ; Initialize service
	       (init_connection_service
	         ; Perform the userauth protocol to login and request
		 ; the ssh-connection service.
		 (lsh_login options (options2identities options)
		   ; Request the userauth service
518
		   (userauth_service
Niels Möller's avatar
Niels Möller committed
519
		     ; Start the ssh transport protocol
520
521
522
523
	             (connection_handshake
		       handshake
		       (options2verifier options
				         (options2known_hosts options))
Niels Möller's avatar
Niels Möller committed
524
525
 		       ; Connect using tcp
		       (connect (options2remote options))))))))))
526
527
528
529
530
531
532
533
534
*/

/* GABA:
   (expr
     (name make_start_session)
     (params
       (open_session object command)
       (requests object object_list))
     (expr (lambda (connection)
Niels Möller's avatar
Niels Möller committed
535
536
537
       ((progn requests)
         ; Create a "session" channel
         (open_session connection)))))
Niels Möller's avatar
Niels Möller committed
538
539
*/

Niels Möller's avatar
Niels Möller committed
540
541
542
543
544
545
546
/* Requests a shell, and connects the channel to our stdio. */
/* GABA:
   (expr
     (name start_shell)
     (super command)
     (expr
       (lambda (session)
Niels Möller's avatar
Niels Möller committed
547
          (client_start_io (request_shell session)))))
Niels Möller's avatar
Niels Möller committed
548
549
*/

Niels Möller's avatar
Niels Möller committed
550
551
552
553
554
/* Parse the argument for -R and -L */
static int
parse_forward_arg(char *arg,
		  UINT32 *listen_port,
		  struct address_info **target)
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
{
  char *first;
  char *second;
  char *end;
  long port;
  
  first = strchr(arg, ':');
  if (!first)
    return 0;

  second = strchr(first + 1, ':');
  if (!second || (second == first + 1))
    return 0;

  if (strchr(second + 1, ':'))
    return 0;

  port = strtol(arg, &end, 0);
  if ( (end == arg)  || (end != first)
       || (port < 0) || (port > 0xffff) )
    return 0;

  *listen_port = port;

  port = strtol(second + 1, &end, 0);
  if ( (end == second + 1) || (*end != '\0')
       || (port < 0) || (port > 0xffff) )
    return 0;

  *target = make_address_info(ssh_format("%ls", second - first - 1, first + 1), port);
  
  return 1;
}
Niels Möller's avatar
Niels Möller committed
588

Niels Möller's avatar
Niels Möller committed
589

590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
/* FIXME: Resetting the tty should really be done by the corresponding
 * channel. */

#if WITH_PTY_SUPPORT
/* Global variable, because we use atexit() rather than on_exit() */
struct tty_state
{
  struct termios mode;
  int fd;
} old_tty;

static void reset_tty(void)
{
  tty_setattr(old_tty.fd, &old_tty.mode);
}

static int remember_tty(int fd)
{
  old_tty.fd = fd;
  return (tty_getattr(fd, &old_tty.mode)
	  && !atexit(reset_tty));
}
#endif /* WITH_PTY_SUPPORT */


/* Option parsing */

617
618
#define ARG_NOT 0x400

619
#define OPT_NO_PUBLICKEY 0x201
620

621
622
623
624
#define OPT_SLOPPY 0x202
#define OPT_STRICT 0x203
#define OPT_CAPTURE 0x204

Niels Möller's avatar
Niels Möller committed
625
626
#define OPT_HOST_DB 0x205

627
628
629
630
static const struct argp_option
main_options[] =
{
  /* Name, key, arg-name, flags, doc, group */
631
632
  { "port", 'p', "Port", 0, "Connect to this port.", 0 },
  { "user", 'l', "User name", 0, "Login as this user.", 0 },
Balázs Scheidler's avatar
Balázs Scheidler committed
633
  { "identity", 'i',  "Identity key", 0, "Use this key to authenticate.", 0 },
634
635
  { "no-publickey", OPT_NO_PUBLICKEY, NULL, 0,
    "Don't try publickey user authentication.", 0 },
Niels Möller's avatar
Niels Möller committed
636
  { "host-db", OPT_HOST_DB, "Filename", 0, "By default, ~/.lsh/known_hosts", 0},
637
638
639
640
641
642
643
  { "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 },
644
645
646
  { NULL, 0, NULL, 0, "Actions:", 0 },
  { "forward-local-port", 'L', "local-port:target-host:target-port", 0, "", 0 },
  { "forward-remote-port", 'R', "remote-port:target-host:target-port", 0, "", 0 },
Niels Möller's avatar
Niels Möller committed
647
  { "nop", 'N', NULL, 0, "No operation (suppresses the default action, "
648
649
650
    "which is to spawn a remote shell)", 0 },
  { NULL, 0, NULL, 0, "Modifiers that apply to port forwarding:", 0 },
  { "remote-peers", 'g', NULL, 0, "Allow remote access to forwarded ports", 0 },
651
  { "no-remote-peers", 'g' | ARG_NOT, NULL, 0, 
652
    "Disallow remote access to forwarded ports (default).", 0 },
653
#if WITH_PTY_SUPPORT
654
655
656
  { NULL, 0, NULL, 0, "Modifiers that apply to remote execution:", 0 },
  { "pty", 't', NULL, 0, "Request a remote pty (default).", 0 },
  { "no-pty", 't' | ARG_NOT, NULL, 0, "Don't request a remote pty.", 0 },
657
#endif /* WITH_PTY_SUPPORT */
658
659
  { NULL, 0, NULL, 0, "Universal not:", 0 },
  { "no", 'n', NULL, 0, "Inverts the effect of the next modifier", 0 },
660
661
662
663
664
665
666
  { NULL, 0, NULL, 0, NULL, 0 }
};


static const struct argp_child
main_argp_children[] =
{
667
  { &algorithms_argp, 0, "", 0 },
668
669
670
671
672
673
674
675
676
677
678
679
680
681
  { &werror_argp, 0, "", 0 },
  { NULL, 0, NULL, 0}
};

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:
682
683
      state->child_inputs[0] = &self->super;
      state->child_inputs[1] = NULL;
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
      break;
    case ARGP_KEY_NO_ARGS:
      argp_usage(state);
      break;
    case ARGP_KEY_ARG:
      if (!state->arg_num)
	{
	  self->remote = make_address_info_c(arg, self->port);
	  
	  if (!self->remote)
	    argp_error(state, "Invalid port or service '%s'.", self->port);

	  break;
	}
      else
	/* Let the next case parse it.  */
	return ARGP_ERR_UNKNOWN;

      break;
    case ARGP_KEY_ARGS:
Niels Möller's avatar
Niels Möller committed
704
      argp_error(state, "Providing a remote command on the command line is not supported yet.");
705
706
707
      break;

    case ARGP_KEY_END:
708
      {
709
710
711
712
713
714
	if (!self->home)
	  {
	    argp_error(state, "No home directory. Please set HOME in the environment.");
	    break;
	  }
	  
715
716
717
718
719
720
	if (!self->user)
	  {
	    argp_error(state, "No user name given. Use the -l option, or set LOGNAME in the environment.");
	    break;
	  }

721
	{
722
723
724
725
726
727
728
	  struct lsh_string *tmp = NULL;
	  const char *s = NULL;
	  
	  if (self->capture)
	    s = self->capture;
	  else if (self->sloppy)
	    {
729
	      tmp = ssh_format("%lz/.lsh/captured_keys%c", self->home, 0);
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
	      s = tmp->data;
	    }
	  if (s)
	    {
	      struct io_fd *f
		= io_write_file(self->backend, s,
				O_CREAT | O_APPEND | O_WRONLY,
				0600, 500, NULL,
				make_report_exception_handler(EXC_IO, EXC_IO,
							      "Writing new ACL: ",
							      &default_exception_handler,
							      HANDLER_CONTEXT));
	      if (f)
		self->capture_file = &f->write_buffer->super;
	      else
		{
		  werror("Failed to open '%z' (errno = %i): %z.\n",
			 s, errno, STRERROR(errno));
		}
	    }
	  lsh_string_free(tmp);
751
	}
752
	
753
#if WITH_TCP_FORWARD
754
755
756
757
758
	if (self->remote_forward)
	  object_queue_add_tail
	    (&self->actions,
	     &make_install_fix_channel_open_handler
	     (ATOM_FORWARDED_TCPIP, &channel_open_forwarded_tcpip)->super);
759
760
#endif /* WITH_TCP_FORWARD */
      
761
762
763
764
765
766
	/* Add shell action */
	if (self->start_shell)
	  {
	    int in;
	    int out;
	    int err;
767

768
	    struct command *get_pty = NULL;
769
	  
770
	    struct object_list *session_requests;
771
772
      
#if WITH_PTY_SUPPORT
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
	    if (self->with_pty)
	      {
		if (tty_fd < 0)
		  {
		    werror("lsh: No tty available.\n");
		  }
		else
		  {
		    if (! (remember_tty(tty_fd)
			   && (get_pty = make_pty_request(tty_fd))))
		      {
			werror("lsh: Can't use tty (probably getattr or atexit() failed.\n");
		      }
		  }
	      }
	    /* FIXME: We need a non-varargs constructor for lists. */
	    if (get_pty)
790
791
792
793
794
795
796
797
	      session_requests
		= make_object_list(2,
				   /* Ignore EXC_CHANNEL_REQUEST for the pty allocation call. */
				   make_catch_apply
				   (make_catch_handler_info(EXC_ALL, EXC_CHANNEL_REQUEST,
							    0, NULL),
				    get_pty),
				   start_shell(), -1);
798
	    else
799
#endif /* WITH_PTY_SUPPORT */
800
	      session_requests = make_object_list(1, start_shell(), -1);
801
	  
802
803
	    in = STDIN_FILENO;
	    out = STDOUT_FILENO;
804
	  
805
806
807
808
809
	    if ( (err = dup(STDERR_FILENO)) < 0)
	      {
		argp_failure(state, EXIT_FAILURE, errno, "Can't dup stderr: %s", STRERROR(errno));
		fatal("Can't happen.\n");
	      }
810

811
	    set_error_stream(STDERR_FILENO, 1);
812
	  
813
814
	    /* Exit code if no session is established */
	    *self->exit_code = 17;
815
	  
816
817
818
819
820
821
822
823
824
825
826
827
828
829
	    object_queue_add_tail
	      (&self->actions,
	       make_start_session
	       (make_open_session_command(make_client_session
					  (io_read(make_io_fd(self->backend, in, self->handler),
						   NULL, NULL),
					   io_write(make_io_fd(self->backend, out, self->handler),
						    BLOCK_SIZE, NULL),
					   io_write(make_io_fd(self->backend, err, self->handler),
						    BLOCK_SIZE, NULL),
					   WINDOW_SIZE,
					   self->exit_code)),
		session_requests));
	  }
830
	  
831
832
833
834
835
	if (object_queue_is_empty(&self->actions))
	  {
	    argp_error(state, "No actions given.");
	    break;
	  }
836

837
838
	break;
      }
Niels Möller's avatar
Niels Möller committed
839

840
841
842
    case 'p':
      self->port = arg;
      break;
Niels Möller's avatar
Niels Möller committed
843

844
845
846
    case 'l':
      self->user = arg;
      break;
Niels Möller's avatar
Niels Möller committed
847

Balázs Scheidler's avatar
Balázs Scheidler committed
848
    case 'i':
849
850
      self->identity = optarg;
      break;
Niels Möller's avatar
Niels Möller committed
851

852
853
854
    case OPT_NO_PUBLICKEY:
      self->publickey = 0;
      break;
855

Niels Möller's avatar
Niels Möller committed
856
857
858
859
    case OPT_HOST_DB:
      self->known_hosts = optarg;
      break;
      
860
861
862
863
864
865
866
867
868
869
870
    case OPT_SLOPPY:
      self->sloppy = 1;
      break;

    case OPT_STRICT:
      self->sloppy = 0;
      break;

    case OPT_CAPTURE:
      self->capture = arg;
      break;
871
      
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
    case 'L':
      {
	UINT32 listen_port;
	struct address_info *target;

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

	object_queue_add_tail(&self->actions,
			      &make_forward_local_port
			      (self->backend,
			       make_address_info((self->with_remote_peers
						  ? NULL
						  : ssh_format("%lz", "127.0.0.1")),
						 listen_port),
			       target)->super);
	break;
      }      

    case 'R':
      {
	UINT32 listen_port;
	struct address_info *target;

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

	object_queue_add_tail(&self->actions,
			      &make_forward_remote_port
			      (self->backend,
			       make_address_info((self->with_remote_peers
						  /* FIXME: Is NULL an ok value? */
						  ? ssh_format("%lz", "0.0.0.0")
						  : ssh_format("%lz", "127.0.0.1")),
						 listen_port),
			       target)->super);
	self->remote_forward = 1;
	break;
      }      

    case 'N':
      self->start_shell = 0;
      break;
    case 'g':
      if (self->not)
	{
	  self->not = 0;

	case 'g' | ARG_NOT:
	  self->with_remote_peers = 0;
	  break;
	}
      
      self->with_remote_peers = 1;
      break;
#if WITH_PTY_SUPPORT
    case 't':
      if (self->not)
	{
	  self->not = 0;

	case 't' | ARG_NOT:
	  self->with_pty = 0;
	  break;
	}
      self->with_pty = 1;
      break;
#endif /* WITH_PTY_SUPPORT */

    case 'n':
      self->not = !self->not;
      break;
    }
  
  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,
960
  NULL, NULL
961
962
};

Niels Möller's avatar
Niels Möller committed
963
964
965
966
967
968
969
970
/* GABA:
   (class
     (name lsh_default_handler)
     (super exception_handler)
     (vars
       (status . "int *")))
*/

Niels Möller's avatar
Niels Möller committed
971
972
973
static void
do_lsh_default_handler(struct exception_handler *s,
		       const struct exception *e)
Niels Möller's avatar
Niels Möller committed
974
975
976
{
  CAST(lsh_default_handler, self, s);

977
  if (e->type & EXC_IO)
Niels Möller's avatar
Niels Möller committed
978
    {
979
      CAST_SUBTYPE(io_exception, exc, e);
980
#if 0
Niels Möller's avatar
Niels Möller committed
981
982
983
984
985
      if (exc->fd)
	close_fd_nicely(exc->fd);
#endif 
      *self->status = EXIT_FAILURE;
      
986
      werror("lsh: %z, (errno = %i)\n", e->msg, exc->error);
Niels Möller's avatar
Niels Möller committed
987
    }
988
989
990
991
992
993
  else
    switch(e->type)
      {
      case EXC_RESOLVE:
      case EXC_AUTH:
      case EXC_SERVICE:
994
995
      case EXC_SEXP_SYNTAX:
      case EXC_SPKI_TYPE:
996
      case EXC_CHANNEL_REQUEST:
997
998
999
1000
	werror("lsh: %z\n", e->msg);
	*self->status = EXIT_FAILURE;
	break;
      default: