tcpforward.c 14.1 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
112
113
114
   (class
     (name tcpip_channel)
     (super ssh_channel)
     (vars
       (socket object io_fd)))
*/

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->super.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)
158
    close_fd(&channel->socket->super, 0);
159
160
161
}

struct ssh_channel *make_tcpip_channel(struct io_fd *socket, UINT32 max_window)
162
163
{
  NEW(tcpip_channel, self);
164
  assert(socket);
165
166
  
  init_channel(&self->super);
167

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

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

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

183
184
185
186
187
/* 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. */
188

Niels Möller's avatar
Niels Möller committed
189
190
191
192
193
194
195
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;
196
  
Niels Möller's avatar
Niels Möller committed
197
198
199
200
201
  /* 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, */
202
		make_channel_read_close_callback(&channel->super));
203
  
Niels Möller's avatar
Niels Möller committed
204
  /* Flow control */
Niels Möller's avatar
Niels Möller committed
205
  channel->socket->write_buffer->report = &channel->super.super;
Niels Möller's avatar
Niels Möller committed
206
207
}

208
209
210

/* Handle channel open requests */

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
/* 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
238
239
240
/* GABA:
   (class
     (name open_forwarded_tcpip_continuation)
241
     (super command_continuation)
Niels Möller's avatar
Niels Möller committed
242
     (vars
243
       (up object command_continuation)
Niels Möller's avatar
Niels Möller committed
244
       (connection object ssh_connection)))
Niels Möller's avatar
Niels Möller committed
245
246
*/

247
248
249
/* 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
250
static void
Niels Möller's avatar
Niels Möller committed
251
252
253
254
255
256
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);

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

259
260
261
  tcpip_channel_start_io(channel);

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

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

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

276

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

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

  struct lsh_string *dest_host;
296
297
298
299
300
  UINT32 dest_port;
  UINT8 *orig_host;
  UINT32 orig_host_length;
  UINT32 orig_port;
  
301
302
303
304
305
  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))
306
    {
Niels Möller's avatar
Niels Möller committed
307
      verbose("direct-tcpip connection attempt\n");
308

Niels Möller's avatar
Niels Möller committed
309
      COMMAND_CALL
310
311
	(closure->callback,
	 make_address_info(dest_host, dest_port),
312
313
314
315
	 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));
316
317
318
    }
  else
    {
319
320
321
      lsh_string_free(dest_host);
      
      werror("do_channel_open_direct_tcpip: Invalid message!\n");
322
      PROTOCOL_ERROR(connection->e, "Invalid CHANNEL_OPEN direct-tcp message.");
323
324
325
    }
}

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

336

337
338
/* Global requests for forwarding */

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

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

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

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

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

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

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

  return &self->super;
}
395
396


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

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

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

Niels Möller's avatar
Niels Möller committed
430
      if (lookup_forward(&connection->table->local_ports,
431
432
433
			 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
434
435
	  GLOBAL_REQUEST_CALLBACK(c, 0);
	  return;
436
	}
437
      
438
      verbose("Adding forward-tcpip\n");
439
      forward = make_local_port(a, NULL);
Niels Möller's avatar
Niels Möller committed
440
      object_queue_add_head(&connection->table->local_ports,
441
			    &forward->super.super);
442
443

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

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

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

      if (port)
495
        {
496
	  assert(port->socket);
497
498
	  verbose("Cancelling a requested tcpip-forward.\n");

499
500
	  close_fd(port->socket, 0);
	  port->socket = NULL;
501

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

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

Niels Möller's avatar
Niels Möller committed
520
521
struct global_request tcpip_cancel_forward =
{ STATIC_HEADER, do_tcpip_cancel_forward }; 
522

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

524
525
/* Remote forwarding */

Niels Möller's avatar
Niels Möller committed
526
527
528
static void
do_channel_open_forwarded_tcpip(struct channel_open *s UNUSED,
				struct ssh_connection *connection,
529
				UINT32 channel_type UNUSED,
Niels Möller's avatar
Niels Möller committed
530
531
532
				struct simple_buffer *args,
				struct command_continuation *c,
				struct exception_handler *e)
533
534
535
536
537
538
539
540
541
542
543
544
545
546
{
  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
547
	   lookup_forward(&connection->table->remote_ports,
548
549
550
			  listen_ip_length, listen_ip, listen_port));
	   
      if (port && port->callback)
Niels Möller's avatar
Niels Möller committed
551
	{
552
	  COMMAND_CALL(port->callback,
553
		       make_address_info(peer_host, peer_port),
554
555
556
557
558
		       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
559
560
	  return;
	}
561
562
563
564
      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
565
566
567
568
      EXCEPTION_RAISE(e,
		      make_channel_open_exception(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
						  "Unexpected tcpip-forward request"));
      return;
569
570
571
572
573
574
    }
  else
    {
      werror("do_channel_open_forwarded_tcpip: Invalid message!\n");

      lsh_string_free(peer_host);
575
      PROTOCOL_ERROR(e, "Invalid tcpip-forward message");
576
577
578
579
580
    }
}

struct channel_open channel_open_forwarded_tcpip =
{ STATIC_HEADER, do_channel_open_forwarded_tcpip};