tcpforward.c 14.2 KB
Newer Older
1
/* tcpforward.c
2
3
4
5
6
7
 *
 * $Id$
 */

/* lsh, an implementation of the ssh protocol
 *
8
 * Copyright (C) 1998 Balzs Scheidler, Niels Mller
9
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
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
 */
24
25
26

#include "tcpforward.h"

27
#include "channel_commands.h"
28
#include "format.h"
29
#include "io_commands.h"
30
31
32
#include "ssh.h"
#include "werror.h"

33
#include <assert.h>
34
35
36
#include <errno.h>
#include <string.h>

37

38
#define GABA_DEFINE
39
#include "tcpforward.h.x"
40
#undef GABA_DEFINE
41
42
43

#include "tcpforward.c.x"

44
45
/* Structures used to keep track of forwarded ports */

46
47
static struct local_port *
make_local_port(struct address_info *address, struct lsh_fd *socket)
48
{
49
  NEW(local_port, self);
50

51
  self->super.listen = address;  
52
  self->socket = socket;
53
54
  return self;
}
55

Niels Möller's avatar
Niels Möller committed
56
struct remote_port *
57
58
59
60
61
62
63
64
65
66
67
make_remote_port(struct address_info *listen,
		 struct command *callback)
{
  NEW(remote_port, self);

  self->super.listen = listen;  
  self->callback = callback;

  return self;
}

68
69
70
71
72
73
static struct forwarded_port *
lookup_forward(struct object_queue *q,
	       UINT32 length, UINT8 *ip, UINT32 port)
{
  FOR_OBJECT_QUEUE(q, n)
    {
74
      CAST_SUBTYPE(forwarded_port, f, n);
75
      
76
      if ( (port == f->listen->port)
Niels Möller's avatar
Niels Möller committed
77
	   && lsh_string_eq_l(f->listen->ip, length, ip) )
78
79
80
81
82
	return f;
    }
  return NULL;
}

83
static struct local_port *
84
85
86
87
88
remove_forward(struct object_queue *q, int null_ok,
	       UINT32 length, UINT8 *ip, UINT32 port)
{
  FOR_OBJECT_QUEUE(q, n)
    {
89
      CAST(local_port, f, n);
90
      
91
      if ( (port == f->super.listen->port)
Niels Möller's avatar
Niels Möller committed
92
	   && lsh_string_eq_l(f->super.listen->ip, length, ip) )
93
94
95
	{
	  if (null_ok || f->socket)
	    {
96
	      FOR_OBJECT_QUEUE_REMOVE(q, n);
97
98
99
100
101
102
103
104
	      return f;
	    }
	  else return NULL;
	}
    }
  return NULL;
}

105
/* TCP forwarding channel */
106

107
/* GABA:
108
109
110
111
   (class
     (name tcpip_channel)
     (super ssh_channel)
     (vars
Niels Möller's avatar
Niels Möller committed
112
       (socket object lsh_fd)))
113
114
*/

Niels Möller's avatar
Niels Möller committed
115
116
117
static void
do_tcpip_receive(struct ssh_channel *c,
		 int type, struct lsh_string *data)
118
119
120
121
122
123
{
  CAST(tcpip_channel, closure, c);
  
  switch (type)
    {
    case CHANNEL_DATA:
124
      A_WRITE(&closure->socket->write_buffer->super, data);
Niels Möller's avatar
Niels Möller committed
125
      break;
126
127
128
    case CHANNEL_STDERR_DATA:
      werror("Ignoring unexpected stderr data.\n");
      lsh_string_free(data);
Niels Möller's avatar
Niels Möller committed
129
      break;
130
131
132
133
134
    default:
      fatal("Internal error. do_tcpip_receive()");
    }
}

Niels Möller's avatar
Niels Möller committed
135
static void
Niels Möller's avatar
Niels Möller committed
136
137
do_tcpip_send(struct ssh_channel *s,
	      struct ssh_connection *c UNUSED)
138
{
Niels Möller's avatar
Niels Möller committed
139
  CAST(tcpip_channel, self, s);
140
  
Niels Möller's avatar
Niels Möller committed
141
  self->socket->want_read = 1;
142
143
}

Niels Möller's avatar
Niels Möller committed
144
145
static void
do_tcpip_eof(struct ssh_channel *c)
146
147
148
{
  if ( (c->flags & CHANNEL_SENT_EOF)
       && (c->flags & CHANNEL_CLOSE_AT_EOF))
Niels Möller's avatar
Niels Möller committed
149
    channel_close(c);
150
}
151

Niels Möller's avatar
Niels Möller committed
152
153
static void
do_tcpip_channel_die(struct ssh_channel *c)
154
155
156
157
{
  CAST(tcpip_channel, channel, c);

  if (channel->socket)
Niels Möller's avatar
Niels Möller committed
158
    close_fd(channel->socket, 0);
159
160
}

Niels Möller's avatar
Niels Möller committed
161
162
struct ssh_channel *
make_tcpip_channel(struct lsh_fd *socket, UINT32 max_window)
163
164
{
  NEW(tcpip_channel, self);
165
  assert(socket);
166
167
  
  init_channel(&self->super);
168

169
170
  /* The rest of the callbacks are not set up until tcpip_start_io. */
  
171
  self->super.close = do_tcpip_channel_die;
172

173
  self->super.max_window = max_window;
174
  self->super.rec_window_size = max_window;
175

176
177
178
  /* FIXME: Make maximum packet size configurable. */
  self->super.rec_max_packet = SSH_MAX_PACKET;
  
179
180
  self->socket = socket;
  
181
  return &self->super;
182
183
}

184
185
186
187
188
/* NOTE: Because this function is called by
 * do_open_forwarded_tcpip_continuation, the same restrictions apply.
 * I.e we can not assume that the channel is completely initialized
 * (channel_open_continuation has not yet done its work), and we can't
 * send any packets. */
189

Niels Möller's avatar
Niels Möller committed
190
191
192
193
194
195
196
void tcpip_channel_start_io(struct ssh_channel *c)
{
  CAST(tcpip_channel, channel, c);

  channel->super.receive = do_tcpip_receive;
  channel->super.send = do_tcpip_send;
  channel->super.eof = do_tcpip_eof;
197
  
Niels Möller's avatar
Niels Möller committed
198
199
200
201
202
  /* Install callbacks on the local socket */
  io_read_write(channel->socket,
		make_channel_read_data(&channel->super),
		/* FIXME: Make this configurable */
		SSH_MAX_PACKET * 10, /* self->block_size, */
203
		make_channel_read_close_callback(&channel->super));
204
  
Niels Möller's avatar
Niels Möller committed
205
  /* Flow control */
Niels Möller's avatar
Niels Möller committed
206
  channel->socket->write_buffer->report = &channel->super.super;
Niels Möller's avatar
Niels Möller committed
207
208
}

209
210
211

/* Handle channel open requests */

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
/* Exception handler that promotes connect and dns errors to
 * CHANNEL_OPEN exceptions */

static void
do_exc_tcip_connect_handler(struct exception_handler *s,
			    const struct exception *e)
{
  switch(e->type)
    {
    case EXC_IO_CONNECT:
    case EXC_RESOLVE:
      EXCEPTION_RAISE(s->parent,
		      make_channel_open_exception(SSH_OPEN_CONNECT_FAILED,
						  e->msg));
      break;
    default:
      EXCEPTION_RAISE(s->parent, e);
    }
}

static struct exception_handler *
make_exc_tcpip_connect_handler(struct exception_handler *parent,
			       const char *context)
{
  return make_exception_handler(do_exc_tcip_connect_handler, parent, context);
}

Niels Möller's avatar
Niels Möller committed
239
240
241
/* GABA:
   (class
     (name open_forwarded_tcpip_continuation)
242
     (super command_continuation)
Niels Möller's avatar
Niels Möller committed
243
     (vars
244
       (up object command_continuation)
Niels Möller's avatar
Niels Möller committed
245
       (connection object ssh_connection)))
Niels Möller's avatar
Niels Möller committed
246
247
*/

248
249
250
/* NOTE: This continuation should not duplicate the work done by
 * channel_open_continuation. It must also not send any packets on the
 * channel, because it is not yet completely initialized. */
Niels Möller's avatar
Niels Möller committed
251
static void
Niels Möller's avatar
Niels Möller committed
252
253
254
255
256
257
do_open_forwarded_tcpip_continuation(struct command_continuation *s,
				     struct lsh_object *x)
{
  CAST(open_forwarded_tcpip_continuation, self, s);
  CAST_SUBTYPE(ssh_channel, channel, x);

258
  assert(channel);
Niels Möller's avatar
Niels Möller committed
259

260
261
262
  tcpip_channel_start_io(channel);

  COMMAND_RETURN(self->up, channel);
Niels Möller's avatar
Niels Möller committed
263
264
265
}

static struct command_continuation *
Niels Möller's avatar
Niels Möller committed
266
make_open_forwarded_tcpip_continuation(struct ssh_connection *connection,
267
				       struct command_continuation *c)
Niels Möller's avatar
Niels Möller committed
268
269
{
  NEW(open_forwarded_tcpip_continuation, self);
270
271
  self->super.c = do_open_forwarded_tcpip_continuation;
  self->up = c;
Niels Möller's avatar
Niels Möller committed
272
  self->connection = connection;
Niels Möller's avatar
Niels Möller committed
273

274
  return &self->super;
Niels Möller's avatar
Niels Möller committed
275
276
}

277

Niels Möller's avatar
Niels Möller committed
278
/* GABA:
279
   (class
280
     (name channel_open_direct_tcpip)
281
282
     (super channel_open)
     (vars
Niels Möller's avatar
Niels Möller committed
283
       (callback object command)))
284
285
*/

Niels Möller's avatar
Niels Möller committed
286
287
288
static void
do_channel_open_direct_tcpip(struct channel_open *s,
			     struct ssh_connection *connection,
289
			     UINT32 channel_type UNUSED,
Niels Möller's avatar
Niels Möller committed
290
			     struct simple_buffer *args,
Niels Möller's avatar
Niels Möller committed
291
292
			     struct command_continuation *c,
			     struct exception_handler *e)
293
{
Niels Möller's avatar
Niels Möller committed
294
  CAST(channel_open_direct_tcpip, closure, s);
295
296

  struct lsh_string *dest_host;
297
298
299
300
301
  UINT32 dest_port;
  UINT8 *orig_host;
  UINT32 orig_host_length;
  UINT32 orig_port;
  
302
303
304
305
306
  if ( (dest_host = parse_string_copy(args))
       && parse_uint32(args, &dest_port) 
       && parse_string(args, &orig_host_length, &orig_host)
       && parse_uint32(args, &orig_port) 
       && parse_eod(args))
307
    {
Niels Möller's avatar
Niels Möller committed
308
      verbose("direct-tcpip connection attempt\n");
309

Niels Möller's avatar
Niels Möller committed
310
      COMMAND_CALL
311
312
	(closure->callback,
	 make_address_info(dest_host, dest_port),
313
314
315
316
	 make_open_forwarded_tcpip_continuation(connection, c), 
	 /* NOTE: This exception handler will be associated with the
	  * fd for its entire lifetime. */
	 make_exc_tcpip_connect_handler(e, HANDLER_CONTEXT));
317
318
319
    }
  else
    {
320
321
322
      lsh_string_free(dest_host);
      
      werror("do_channel_open_direct_tcpip: Invalid message!\n");
323
      PROTOCOL_ERROR(connection->e, "Invalid CHANNEL_OPEN direct-tcp message.");
324
325
326
    }
}

Niels Möller's avatar
Niels Möller committed
327
328
struct channel_open *
make_channel_open_direct_tcpip(struct command *callback)
329
{
330
  NEW(channel_open_direct_tcpip, self);
331
  
332
  self->super.handler = do_channel_open_direct_tcpip;
Niels Möller's avatar
Niels Möller committed
333
  self->callback = callback;
334
335
336
  return &self->super;
}

337

338
339
/* Global requests for forwarding */

340
341
/* GABA:
   (class
Niels Möller's avatar
Niels Möller committed
342
     (name tcpip_forward_request_continuation)
343
     (super command_continuation)
344
     (vars
345
       (connection object ssh_connection)
346
       (forward object local_port)
347
       (c object global_request_callback)))
348
349
*/

Niels Möller's avatar
Niels Möller committed
350
351
/* FIXME: Split off an exception handler */
static void
Niels Möller's avatar
Niels Möller committed
352
353
do_tcpip_forward_request_continuation(struct command_continuation *c,
				      struct lsh_object *x)
354
{
Niels Möller's avatar
Niels Möller committed
355
  CAST(tcpip_forward_request_continuation, self, c);
356
  CAST_SUBTYPE(lsh_fd, fd, x);
357

358
  assert(self->forward);
359
  
360
  if (!fd)
361
    {
362
      struct local_port *port
Niels Möller's avatar
Niels Möller committed
363
	= remove_forward(&self->connection->table->local_ports,
364
			 1,
365
366
367
			 self->forward->super.listen->ip->length,
			 self->forward->super.listen->ip->data,
			 self->forward->super.listen->port);
368
369
370
      assert(port);
      assert(port == self->forward);
      
Niels Möller's avatar
Niels Möller committed
371
      GLOBAL_REQUEST_CALLBACK(self->c, 0);
372
373
    }
  
374
  REMEMBER_RESOURCE(self->connection->resources, &fd->super);
375

376
  self->forward->socket = fd;
377

Niels Möller's avatar
Niels Möller committed
378
  GLOBAL_REQUEST_CALLBACK(self->c, 1);
379
380
}

381
static struct command_continuation *
Niels Möller's avatar
Niels Möller committed
382
383
384
make_tcpip_forward_request_continuation(struct ssh_connection *connection,
					struct local_port *forward,
					struct global_request_callback *c)
385
{
Niels Möller's avatar
Niels Möller committed
386
  NEW(tcpip_forward_request_continuation, self);
387

388
389
390
  self->connection = connection;
  self->forward = forward;
  self->c = c;
391
  
Niels Möller's avatar
Niels Möller committed
392
  self->super.c = do_tcpip_forward_request_continuation;
393
394
395

  return &self->super;
}
396
397


398
/* GABA:
399
400
401
402
   (class
     (name tcpip_forward_request)
     (super global_request)
     (vars
Niels Möller's avatar
Niels Möller committed
403
404
       (callback object command)))
       ;; (backend object io_backend)))
405
406
*/

Niels Möller's avatar
Niels Möller committed
407
408
409
410
411
static void
do_tcpip_forward_request(struct global_request *s, 
			 struct ssh_connection *connection,
			 struct simple_buffer *args,
			 struct global_request_callback *c)
412
{
413
414
  CAST(tcpip_forward_request, self, s);
  struct lsh_string *bind_host;
415
416
  UINT32 bind_port;
  
417
  if ((bind_host = parse_string_copy(args)) 
418
419
420
      && parse_uint32(args, &bind_port) 
      && parse_eod(args))
    {
421
      struct address_info *a = make_address_info(bind_host, bind_port);
422
      struct local_port *forward;
423
424
425
426

      if (bind_port < 1024)
	{
	  werror("Denying forwarding of privileged port %i.\n", bind_port);
Niels Möller's avatar
Niels Möller committed
427
428
	  GLOBAL_REQUEST_CALLBACK(c, 0);
	  return;
429
	}
430

Niels Möller's avatar
Niels Möller committed
431
      if (lookup_forward(&connection->table->local_ports,
432
433
434
			 bind_host->length, bind_host->data, bind_port))
	{
	  verbose("An already requested tcp-forward requested again\n");
Niels Möller's avatar
Niels Möller committed
435
436
	  GLOBAL_REQUEST_CALLBACK(c, 0);
	  return;
437
	}
438
      
439
      verbose("Adding forward-tcpip\n");
440
      forward = make_local_port(a, NULL);
Niels Möller's avatar
Niels Möller committed
441
      object_queue_add_head(&connection->table->local_ports,
442
			    &forward->super.super);
443
444

      {
Niels Möller's avatar
Niels Möller committed
445
446
447
	COMMAND_CALL(self->callback,
		     a,
		     make_tcpip_forward_request_continuation
448
		     (connection, forward, c),
Niels Möller's avatar
Niels Möller committed
449
450
451
		     /* FIXME: Use a better exception handler.
		      * We should handle EXC_RESOLVE and EXC_LISTEN, and
		      * report or ignore i/o-errors. */
Niels Möller's avatar
Niels Möller committed
452
453
454
455
		     &default_exception_handler
		     /* make_tcpip_forward_request_raise
			(connection, forward) */ );
	return;
456
      }
457
458
459
    }
  else
    {
460
      werror("Incorrectly formatted tcpip-forward request\n");
461
      PROTOCOL_ERROR(connection->e, "Invalid tcpip-forward message.");
462
463
464
    }
}

Niels Möller's avatar
Niels Möller committed
465
struct global_request *make_tcpip_forward_request(struct command *callback)
466
467
468
469
{
  NEW(tcpip_forward_request, self);
  
  self->super.handler = do_tcpip_forward_request;
Niels Möller's avatar
Niels Möller committed
470
  self->callback = callback;
471
  
472
473
474
  return &self->super;
}

Niels Möller's avatar
Niels Möller committed
475
476
477
478
479
static void
do_tcpip_cancel_forward(struct global_request *s UNUSED, 
			struct ssh_connection *connection,
			struct simple_buffer *args,
			struct global_request_callback *c)
480
{
481
482
  UINT32 bind_host_length;
  UINT8 *bind_host;
483
484
  UINT32 bind_port;
  
485
  if (parse_string(args, &bind_host_length, &bind_host) &&
486
487
488
      parse_uint32(args, &bind_port) &&
      parse_eod(args))
    {
Niels Möller's avatar
Niels Möller committed
489
490
      /* FIXME: using null_ok == 0 is not quite right, if the
       * forwarding was requested with want_reply == 0 */
491
      struct local_port *port
Niels Möller's avatar
Niels Möller committed
492
	= remove_forward(&connection->table->local_ports, 0,
493
494
			 bind_host_length,
			 bind_host,
495
496
497
			 bind_port);

      if (port)
498
        {
499
	  assert(port->socket);
500
501
	  verbose("Cancelling a requested tcpip-forward.\n");

502
503
	  close_fd(port->socket, 0);
	  port->socket = NULL;
504

Niels Möller's avatar
Niels Möller committed
505
506
	  GLOBAL_REQUEST_CALLBACK(c, 1);
	  return;
507
	}
508
      else
509
510
	{      
	  verbose("Could not find tcpip-forward to cancel\n");
511

Niels Möller's avatar
Niels Möller committed
512
513
	  GLOBAL_REQUEST_CALLBACK(c, 0);
	  return;
514
	}
515
516
517
    }
  else
    {
518
      werror("Incorrectly formatted cancel-tcpip-forward request\n");
519
      PROTOCOL_ERROR(connection->e, "Invalid cancel-tcpip-forward message.");
520
521
522
    }
}

Niels Möller's avatar
Niels Möller committed
523
524
struct global_request tcpip_cancel_forward =
{ STATIC_HEADER, do_tcpip_cancel_forward }; 
525

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

527
528
/* Remote forwarding */

Niels Möller's avatar
Niels Möller committed
529
530
531
static void
do_channel_open_forwarded_tcpip(struct channel_open *s UNUSED,
				struct ssh_connection *connection,
532
				UINT32 channel_type UNUSED,
Niels Möller's avatar
Niels Möller committed
533
534
535
				struct simple_buffer *args,
				struct command_continuation *c,
				struct exception_handler *e)
536
537
538
539
540
541
542
543
544
545
546
547
548
549
{
  UINT32 listen_ip_length;
  UINT8 *listen_ip;
  UINT32 listen_port;
  struct lsh_string *peer_host = NULL;
  UINT32 peer_port;

  if (parse_string(args, &listen_ip_length, &listen_ip)
      && parse_uint32(args, &listen_port)
      && (peer_host = parse_string_copy(args))
      && parse_uint32(args, &peer_port)
      && parse_eod(args))
    {
      CAST(remote_port, port,
Niels Möller's avatar
Niels Möller committed
550
	   lookup_forward(&connection->table->remote_ports,
551
552
553
			  listen_ip_length, listen_ip, listen_port));
	   
      if (port && port->callback)
Niels Möller's avatar
Niels Möller committed
554
	{
555
	  COMMAND_CALL(port->callback,
556
		       make_address_info(peer_host, peer_port),
557
558
559
560
561
		       make_open_forwarded_tcpip_continuation(connection, c),
		       /* NOTE: This exception handler will be
			* associated with the fd for its entire
			* lifetime. */
		       make_exc_tcpip_connect_handler(e, HANDLER_CONTEXT));
Niels Möller's avatar
Niels Möller committed
562
563
	  return;
	}
564
565
566
567
      werror("Received a forwarded-tcpip request on a port for which we\n"
	     "haven't requested forwarding. Denying.\n");

      lsh_string_free(peer_host);
Niels Möller's avatar
Niels Möller committed
568
569
570
571
      EXCEPTION_RAISE(e,
		      make_channel_open_exception(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
						  "Unexpected tcpip-forward request"));
      return;
572
573
574
575
576
577
    }
  else
    {
      werror("do_channel_open_forwarded_tcpip: Invalid message!\n");

      lsh_string_free(peer_host);
578
      PROTOCOL_ERROR(e, "Invalid tcpip-forward message");
579
580
581
582
583
    }
}

struct channel_open channel_open_forwarded_tcpip =
{ STATIC_HEADER, do_channel_open_forwarded_tcpip};