tcpforward.c 13.5 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 "channel_forward.h"
29
#include "format.h"
30
#include "io_commands.h"
31
32
33
#include "ssh.h"
#include "werror.h"

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

38

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

#include "tcpforward.c.x"

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

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

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

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

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

  return self;
}

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

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

106
107
108

/* Handle channel open requests */

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
135
/* 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
136
137
138
/* GABA:
   (class
     (name open_forwarded_tcpip_continuation)
139
     (super command_continuation)
Niels Möller's avatar
Niels Möller committed
140
     (vars
141
       (up object command_continuation)
Niels Möller's avatar
Niels Möller committed
142
       (connection object ssh_connection)))
Niels Möller's avatar
Niels Möller committed
143
144
*/

145
146
147
/* 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
148
static void
Niels Möller's avatar
Niels Möller committed
149
150
151
152
do_open_forwarded_tcpip_continuation(struct command_continuation *s,
				     struct lsh_object *x)
{
  CAST(open_forwarded_tcpip_continuation, self, s);
Niels Möller's avatar
Niels Möller committed
153
  CAST(channel_forward, channel, x);
Niels Möller's avatar
Niels Möller committed
154

155
  assert(channel);
Niels Möller's avatar
Niels Möller committed
156

157
  channel_forward_start_io(channel);
158
159

  COMMAND_RETURN(self->up, channel);
Niels Möller's avatar
Niels Möller committed
160
161
162
}

static struct command_continuation *
Niels Möller's avatar
Niels Möller committed
163
make_open_forwarded_tcpip_continuation(struct ssh_connection *connection,
164
				       struct command_continuation *c)
Niels Möller's avatar
Niels Möller committed
165
166
{
  NEW(open_forwarded_tcpip_continuation, self);
167
168
  self->super.c = do_open_forwarded_tcpip_continuation;
  self->up = c;
Niels Möller's avatar
Niels Möller committed
169
  self->connection = connection;
Niels Möller's avatar
Niels Möller committed
170

171
  return &self->super;
Niels Möller's avatar
Niels Möller committed
172
173
}

174

Niels Möller's avatar
Niels Möller committed
175
/* GABA:
176
   (class
177
     (name channel_open_direct_tcpip)
178
179
     (super channel_open)
     (vars
Niels Möller's avatar
Niels Möller committed
180
       (callback object command)))
181
182
*/

Niels Möller's avatar
Niels Möller committed
183
184
185
static void
do_channel_open_direct_tcpip(struct channel_open *s,
			     struct ssh_connection *connection,
186
			     struct channel_open_info *info UNUSED,
Niels Möller's avatar
Niels Möller committed
187
			     struct simple_buffer *args,
Niels Möller's avatar
Niels Möller committed
188
189
			     struct command_continuation *c,
			     struct exception_handler *e)
190
{
Niels Möller's avatar
Niels Möller committed
191
  CAST(channel_open_direct_tcpip, closure, s);
192
193

  struct lsh_string *dest_host;
194
  UINT32 dest_port;
Niels Möller's avatar
Niels Möller committed
195
  const UINT8 *orig_host;
196
197
198
  UINT32 orig_host_length;
  UINT32 orig_port;
  
199
200
201
202
203
  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))
204
    {
Niels Möller's avatar
Niels Möller committed
205
      verbose("direct-tcpip connection attempt\n");
206

Niels Möller's avatar
Niels Möller committed
207
208
209
210
211
212
      COMMAND_CALL(closure->callback,
		   make_address_info(dest_host, dest_port),
		   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));
213
214
215
    }
  else
    {
216
217
218
      lsh_string_free(dest_host);
      
      werror("do_channel_open_direct_tcpip: Invalid message!\n");
219
      PROTOCOL_ERROR(connection->e, "Invalid CHANNEL_OPEN direct-tcp message.");
220
221
222
    }
}

Niels Möller's avatar
Niels Möller committed
223
224
struct channel_open *
make_channel_open_direct_tcpip(struct command *callback)
225
{
226
  NEW(channel_open_direct_tcpip, self);
227
  
228
  self->super.handler = do_channel_open_direct_tcpip;
Niels Möller's avatar
Niels Möller committed
229
  self->callback = callback;
230
231
232
  return &self->super;
}

233

234
235
/* Global requests for forwarding */

236
237
/* GABA:
   (class
Niels Möller's avatar
Niels Möller committed
238
     (name tcpip_forward_request_continuation)
239
     (super command_continuation)
240
     (vars
241
       (forward object local_port)
242
       (c object command_continuation)))
243
244
*/

Niels Möller's avatar
Niels Möller committed
245
static void
Niels Möller's avatar
Niels Möller committed
246
247
do_tcpip_forward_request_continuation(struct command_continuation *c,
				      struct lsh_object *x)
248
{
Niels Möller's avatar
Niels Möller committed
249
  CAST(tcpip_forward_request_continuation, self, c);
250
  CAST(lsh_fd, fd, x);
251

252
  assert(self->forward);
253
254
  assert(fd);

255
  self->forward->socket = fd;
256

257
  COMMAND_RETURN(self->c, &self->forward->super.super);
258
259
}

260
static struct command_continuation *
261
make_tcpip_forward_request_continuation(struct local_port *forward,
262
					struct command_continuation *c)
263
{
Niels Möller's avatar
Niels Möller committed
264
  NEW(tcpip_forward_request_continuation, self);
265

266
267
  self->forward = forward;
  self->c = c;
268
  
Niels Möller's avatar
Niels Möller committed
269
  self->super.c = do_tcpip_forward_request_continuation;
270
271
272

  return &self->super;
}
273

274
275
276
277
278
279
280
281
/* GABA:
   (class
     (name tcpip_forward_request_handler)
     (super exception_handler)
     (vars
       (connection object ssh_connection)
       (forward object local_port)))
*/
282

283
static void
284
do_tcpip_forward_request_exc(struct exception_handler *s,
285
286
			     const struct exception *e)
{
287
288
  CAST(tcpip_forward_request_handler, self, s);
  
289
290
291
292
  switch(e->type)
    {
    case EXC_IO_LISTEN:
    case EXC_RESOLVE:
293
294
295
296
297
298
299
300
301
302
303
304
305
306
      {
	struct local_port *port
	  = remove_forward(&self->connection->table->local_ports,
			   1,
			   self->forward->super.listen->ip->length,
			   self->forward->super.listen->ip->data,
			   self->forward->super.listen->port);
	assert(port);
	assert(port == self->forward);
	EXCEPTION_RAISE(s->parent,
			make_simple_exception(EXC_GLOBAL_REQUEST,
					      e->msg));
	break;
      }
307
308
309
310
311
312
313
    default:
      if (e->type & EXC_IO)
	{
	  werror("I/O error on forwarded connection: %z\n",
		 e->msg);
	}
      else
314
	EXCEPTION_RAISE(self->super.parent, e);
315
316
317
318
    }
}

static struct exception_handler *
319
320
321
make_tcpip_forward_request_exc(struct ssh_connection *connection,
			       struct local_port *forward,
			       struct exception_handler *parent,
322
323
			       const char *context)
{
324
325
326
327
328
329
330
331
332
  NEW(tcpip_forward_request_handler, self);
  self->super.raise = do_tcpip_forward_request_exc;
  self->super.parent = parent;
  self->super.context = context;

  self->connection = connection;
  self->forward = forward;

  return &self->super;
333
334
}

335
/* GABA:
336
337
338
339
   (class
     (name tcpip_forward_request)
     (super global_request)
     (vars
340
341
342
       ; The callback is invoked for each request, with the port as
       ; argument. If successful, it should return the fd object
       ; associated with the listening port. It need not remember the port;
343
       ; the continuation installed by do_tcpip_forward_request
344
       ; takes care of that.
Niels Möller's avatar
Niels Möller committed
345
       (callback object command)))
346
347
*/

Niels Möller's avatar
Niels Möller committed
348
349
350
static void
do_tcpip_forward_request(struct global_request *s, 
			 struct ssh_connection *connection,
351
352
			 UINT32 type UNUSED,
			 int want_reply UNUSED,
Niels Möller's avatar
Niels Möller committed
353
			 struct simple_buffer *args,
354
355
			 struct command_continuation *c,
			 struct exception_handler *e)
356
{
357
358
  CAST(tcpip_forward_request, self, s);
  struct lsh_string *bind_host;
359
360
  UINT32 bind_port;
  
361
  if ((bind_host = parse_string_copy(args)) 
362
363
364
      && parse_uint32(args, &bind_port) 
      && parse_eod(args))
    {
365
      struct address_info *a = make_address_info(bind_host, bind_port);
366
      struct local_port *forward;
367
368
369
370

      if (bind_port < 1024)
	{
	  werror("Denying forwarding of privileged port %i.\n", bind_port);
371
	  COMMAND_RETURN(c, NULL);
Niels Möller's avatar
Niels Möller committed
372
	  return;
373
	}
374

Niels Möller's avatar
Niels Möller committed
375
      if (lookup_forward(&connection->table->local_ports,
376
377
			 bind_host->length, bind_host->data, bind_port))
	{
378
379
380
	  static const struct exception again = 
	    STATIC_EXCEPTION(EXC_GLOBAL_REQUEST, "An already requested tcp-forward requested again");

381
	  verbose("An already requested tcp-forward requested again\n");
382
	  EXCEPTION_RAISE(e, &again);
Niels Möller's avatar
Niels Möller committed
383
	  return;
384
	}
385
      
386
      verbose("Adding forward-tcpip\n");
387
      forward = make_local_port(a, NULL);
Niels Möller's avatar
Niels Möller committed
388
      object_queue_add_head(&connection->table->local_ports,
389
			    &forward->super.super);
390
391

      {
Niels Möller's avatar
Niels Möller committed
392
393
	COMMAND_CALL(self->callback,
		     a,
394
395
396
		     make_tcpip_forward_request_continuation(forward, c),
		     make_tcpip_forward_request_exc(connection, forward,
						    e, HANDLER_CONTEXT));
397
	
Niels Möller's avatar
Niels Möller committed
398
	return;
399
      }
400
401
402
    }
  else
    {
403
      werror("Incorrectly formatted tcpip-forward request\n");
404
      PROTOCOL_ERROR(e, "Invalid tcpip-forward message.");
405
406
407
    }
}

Niels Möller's avatar
Niels Möller committed
408
struct global_request *make_tcpip_forward_request(struct command *callback)
409
410
411
412
{
  NEW(tcpip_forward_request, self);
  
  self->super.handler = do_tcpip_forward_request;
Niels Möller's avatar
Niels Möller committed
413
  self->callback = callback;
414
  
415
416
417
  return &self->super;
}

Niels Möller's avatar
Niels Möller committed
418
419
420
static void
do_tcpip_cancel_forward(struct global_request *s UNUSED, 
			struct ssh_connection *connection,
421
422
			UINT32 type UNUSED,
			int want_reply UNUSED,
Niels Möller's avatar
Niels Möller committed
423
			struct simple_buffer *args,
424
425
			struct command_continuation *c,
			struct exception_handler *e)
426
{
427
  UINT32 bind_host_length;
Niels Möller's avatar
Niels Möller committed
428
  const UINT8 *bind_host;
429
430
  UINT32 bind_port;
  
431
  if (parse_string(args, &bind_host_length, &bind_host) &&
432
433
434
      parse_uint32(args, &bind_port) &&
      parse_eod(args))
    {
435
436
437
      /* FIXME: Using null_ok == 0 is not quite right. If the
       * tcpip_forward_hook doesn't return immediately, and we receive
       * a cancel request before the forwarding is setup (which should
Niels Möller's avatar
Niels Möller committed
438
439
       * be ok, if the forwarding was requested with want_reply == 0),
       * cancelling fails and the client has to try again later. */
440

441
      struct local_port *port
Niels Möller's avatar
Niels Möller committed
442
	= remove_forward(&connection->table->local_ports, 0,
443
444
			 bind_host_length,
			 bind_host,
445
446
447
			 bind_port);

      if (port)
448
        {
449
	  assert(port->socket);
450
451
	  verbose("Cancelling a requested tcpip-forward.\n");

Niels Möller's avatar
Niels Möller committed
452
	  close_fd(port->socket);
453
	  port->socket = NULL;
454

455
	  COMMAND_RETURN(c, NULL);
Niels Möller's avatar
Niels Möller committed
456
	  return;
457
	}
458
      else
459
	{      
460
461
	  static const struct exception notfound = 
	    STATIC_EXCEPTION(EXC_GLOBAL_REQUEST, "Could not find tcpip-forward to cancel");
462
	  verbose("Could not find tcpip-forward to cancel\n");
463

464
	  EXCEPTION_RAISE(e, &notfound);
Niels Möller's avatar
Niels Möller committed
465
	  return;
466
	}
467
468
469
    }
  else
    {
470
      werror("Incorrectly formatted cancel-tcpip-forward request\n");
471
      PROTOCOL_ERROR(connection->e, "Invalid cancel-tcpip-forward message.");
472
473
474
    }
}

Niels Möller's avatar
Niels Möller committed
475
476
struct global_request tcpip_cancel_forward =
{ STATIC_HEADER, do_tcpip_cancel_forward }; 
477

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

479
480
/* Remote forwarding */

Niels Möller's avatar
Niels Möller committed
481
482
483
static void
do_channel_open_forwarded_tcpip(struct channel_open *s UNUSED,
				struct ssh_connection *connection,
484
				struct channel_open_info *info UNUSED,
Niels Möller's avatar
Niels Möller committed
485
486
487
				struct simple_buffer *args,
				struct command_continuation *c,
				struct exception_handler *e)
488
489
{
  UINT32 listen_ip_length;
Niels Möller's avatar
Niels Möller committed
490
  const UINT8 *listen_ip;
491
492
493
494
495
496
497
498
499
500
501
  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
502
	   lookup_forward(&connection->table->remote_ports,
503
504
505
			  listen_ip_length, listen_ip, listen_port));
	   
      if (port && port->callback)
Niels Möller's avatar
Niels Möller committed
506
	{
507
	  COMMAND_CALL(port->callback,
508
		       make_address_info(peer_host, peer_port),
509
510
511
512
513
		       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
514
515
	  return;
	}
516
517
518
519
      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
520
521
522
523
      EXCEPTION_RAISE(e,
		      make_channel_open_exception(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
						  "Unexpected tcpip-forward request"));
      return;
524
525
526
527
528
529
    }
  else
    {
      werror("do_channel_open_forwarded_tcpip: Invalid message!\n");

      lsh_string_free(peer_host);
530
      PROTOCOL_ERROR(e, "Invalid tcpip-forward message");
531
532
533
534
535
    }
}

struct channel_open channel_open_forwarded_tcpip =
{ STATIC_HEADER, do_channel_open_forwarded_tcpip};