Coverage Report

Created: 2026-01-05 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dropbear/src/svr-tcpfwd.c
Line
Count
Source
1
/*
2
 * Dropbear SSH
3
 * 
4
 * Copyright (c) 2002,2003 Matt Johnston
5
 * Copyright (c) 2004 by Mihnea Stoenescu
6
 * All rights reserved.
7
 * 
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 * 
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
 * SOFTWARE. */
25
26
#include "includes.h"
27
#include "ssh.h"
28
#include "tcpfwd.h"
29
#include "dbutil.h"
30
#include "session.h"
31
#include "buffer.h"
32
#include "packet.h"
33
#include "listener.h"
34
#include "runopts.h"
35
#include "auth.h"
36
#include "netio.h"
37
38
#if !DROPBEAR_SVR_REMOTETCPFWD
39
40
/* This is better than SSH_MSG_UNIMPLEMENTED */
41
void recv_msg_global_request_remotetcp() {
42
  unsigned int wantreply = 0;
43
44
  TRACE(("recv_msg_global_request_remotetcp: remote tcp forwarding not compiled in"))
45
46
  buf_eatstring(ses.payload);
47
  wantreply = buf_getbool(ses.payload);
48
  if (wantreply) {
49
    send_msg_request_failure();
50
  }
51
}
52
53
/* */
54
#endif /* !DROPBEAR_SVR_REMOTETCPFWD */
55
56
static int svr_cancelremotetcp(void);
57
static int svr_remotetcpreq(int *allocated_listen_port);
58
static int newtcpdirect(struct Channel * channel);
59
#if DROPBEAR_SVR_LOCALSTREAMFWD
60
static int newstreamlocal(struct Channel * channel);
61
#endif
62
63
#if DROPBEAR_SVR_REMOTETCPFWD
64
static const struct ChanType svr_chan_tcpremote = {
65
  "forwarded-tcpip",
66
  NULL,
67
  NULL,
68
  NULL,
69
  NULL,
70
  NULL
71
};
72
73
/* At the moment this is completely used for tcp code (with the name reflecting
74
 * that). If new request types are added, this should be replaced with code
75
 * similar to the request-switching in chansession.c */
76
0
void recv_msg_global_request_remotetcp() {
77
78
0
  char* reqname = NULL;
79
0
  unsigned int namelen;
80
0
  unsigned int wantreply = 0;
81
0
  int ret = DROPBEAR_FAILURE;
82
83
0
  TRACE(("enter recv_msg_global_request_remotetcp"))
84
85
0
  reqname = buf_getstring(ses.payload, &namelen);
86
0
  wantreply = buf_getbool(ses.payload);
87
88
0
  if (svr_opts.noremotetcp || !svr_pubkey_allows_tcpfwd()) {
89
0
    TRACE(("leave recv_msg_global_request_remotetcp: remote tcp forwarding disabled"))
90
0
    goto out;
91
0
  }
92
93
0
  if (namelen > MAX_NAME_LEN) {
94
0
    TRACE(("name len is wrong: %d", namelen))
95
0
    goto out;
96
0
  }
97
98
0
  if (strcmp("tcpip-forward", reqname) == 0) {
99
0
    int allocated_listen_port = 0;
100
0
    ret = svr_remotetcpreq(&allocated_listen_port);
101
    /* client expects-port-number-to-make-use-of-server-allocated-ports */
102
0
    if (DROPBEAR_SUCCESS == ret) {
103
0
      CHECKCLEARTOWRITE();
104
0
      buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS);
105
0
      buf_putint(ses.writepayload, allocated_listen_port);
106
0
      encrypt_packet();
107
0
      wantreply = 0; /* avoid out: below sending another reply */
108
0
    }
109
0
  } else if (strcmp("cancel-tcpip-forward", reqname) == 0) {
110
0
    ret = svr_cancelremotetcp();
111
0
  } else {
112
0
    TRACE(("reqname isn't tcpip-forward: '%s'", reqname))
113
0
  }
114
115
0
out:
116
0
  if (wantreply) {
117
0
    if (ret == DROPBEAR_SUCCESS) {
118
0
      send_msg_request_success();
119
0
    } else {
120
0
      send_msg_request_failure();
121
0
    }
122
0
  }
123
124
0
  m_free(reqname);
125
126
0
  TRACE(("leave recv_msg_global_request"))
127
0
}
128
129
0
static int matchtcp(const void* typedata1, const void* typedata2) {
130
131
0
  const struct TCPListener *info1 = (struct TCPListener*)typedata1;
132
0
  const struct TCPListener *info2 = (struct TCPListener*)typedata2;
133
134
0
  return (info1->listenport == info2->listenport)
135
0
      && (info1->chantype == info2->chantype)
136
0
      && (strcmp(info1->listenaddr, info2->listenaddr) == 0);
137
0
}
138
139
0
static int svr_cancelremotetcp() {
140
141
0
  int ret = DROPBEAR_FAILURE;
142
0
  char * bindaddr = NULL;
143
0
  unsigned int addrlen;
144
0
  unsigned int port;
145
0
  struct Listener * listener = NULL;
146
0
  struct TCPListener tcpinfo;
147
148
0
  TRACE(("enter cancelremotetcp"))
149
150
0
  bindaddr = buf_getstring(ses.payload, &addrlen);
151
0
  if (addrlen > MAX_HOST_LEN) {
152
0
    TRACE(("addr len too long: %d", addrlen))
153
0
    goto out;
154
0
  }
155
156
0
  port = buf_getint(ses.payload);
157
158
0
  tcpinfo.sendaddr = NULL;
159
0
  tcpinfo.sendport = 0;
160
0
  tcpinfo.listenaddr = bindaddr;
161
0
  tcpinfo.listenport = port;
162
0
  listener = get_listener(CHANNEL_ID_TCPFORWARDED, &tcpinfo, matchtcp);
163
0
  if (listener) {
164
0
    remove_listener( listener );
165
0
    ret = DROPBEAR_SUCCESS;
166
0
  }
167
168
0
out:
169
0
  m_free(bindaddr);
170
0
  TRACE(("leave cancelremotetcp"))
171
0
  return ret;
172
0
}
173
174
0
static int svr_remotetcpreq(int *allocated_listen_port) {
175
176
0
  int ret = DROPBEAR_FAILURE;
177
0
  char * request_addr = NULL;
178
0
  unsigned int addrlen;
179
0
  struct TCPListener *tcpinfo = NULL;
180
0
  unsigned int port;
181
0
  struct Listener *listener = NULL;
182
183
0
  TRACE(("enter remotetcpreq"))
184
185
0
  request_addr = buf_getstring(ses.payload, &addrlen);
186
0
  if (addrlen > MAX_HOST_LEN) {
187
0
    TRACE(("addr len too long: %d", addrlen))
188
0
    goto out;
189
0
  }
190
191
0
  port = buf_getint(ses.payload);
192
193
0
  if (port != 0) {
194
0
    if (port < 1 || port > 65535) {
195
0
      TRACE(("invalid port: %d", port))
196
0
      goto out;
197
0
    }
198
199
0
    if (!ses.allowprivport && port < IPPORT_RESERVED) {
200
0
      TRACE(("can't assign port < 1024 for non-root"))
201
0
      goto out;
202
0
    }
203
0
  }
204
205
0
  tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener));
206
0
  tcpinfo->sendaddr = NULL;
207
0
  tcpinfo->sendport = 0;
208
0
  tcpinfo->listenport = port;
209
0
  tcpinfo->chantype = &svr_chan_tcpremote;
210
0
  tcpinfo->tcp_type = forwarded;
211
0
  tcpinfo->interface = svr_opts.interface;
212
213
0
  tcpinfo->request_listenaddr = request_addr;
214
0
  if (!opts.listen_fwd_all || (strcmp(request_addr, "localhost") == 0) ) {
215
    /* NULL means "localhost only" */
216
0
    tcpinfo->listenaddr = NULL;
217
0
  }
218
0
  else
219
0
  {
220
0
    tcpinfo->listenaddr = m_strdup(request_addr);
221
0
  }
222
223
0
  ret = listen_tcpfwd(tcpinfo, &listener);
224
0
  if (DROPBEAR_SUCCESS == ret) {
225
0
    tcpinfo->listenport = get_sock_port(listener->socks[0]);
226
0
    *allocated_listen_port = tcpinfo->listenport;
227
0
  }
228
229
0
out:
230
0
  if (ret == DROPBEAR_FAILURE) {
231
    /* we only free it if a listener wasn't created, since the listener
232
     * has to remember it if it's to be cancelled */
233
0
    m_free(request_addr);
234
0
    m_free(tcpinfo);
235
0
  }
236
237
0
  TRACE(("leave remotetcpreq"))
238
239
0
  return ret;
240
0
}
241
242
#endif /* DROPBEAR_SVR_REMOTETCPFWD */
243
244
#if DROPBEAR_SVR_LOCALTCPFWD
245
246
const struct ChanType svr_chan_tcpdirect = {
247
  "direct-tcpip",
248
  newtcpdirect, /* init */
249
  NULL, /* checkclose */
250
  NULL, /* reqhandler */
251
  NULL, /* closehandler */
252
  NULL /* cleanup */
253
};
254
255
/* Called upon creating a new direct tcp channel (ie we connect out to an
256
 * address */
257
0
static int newtcpdirect(struct Channel * channel) {
258
259
0
  char* desthost = NULL;
260
0
  unsigned int destport;
261
0
  char* orighost = NULL;
262
0
  unsigned int origport;
263
0
  char portstring[NI_MAXSERV];
264
0
  unsigned int len;
265
0
  int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
266
267
0
  TRACE(("newtcpdirect channel %d", channel->index))
268
269
0
  if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) {
270
0
    TRACE(("leave newtcpdirect: local tcp forwarding disabled"))
271
0
    goto out;
272
0
  }
273
274
0
  desthost = buf_getstring(ses.payload, &len);
275
0
  if (len > MAX_HOST_LEN) {
276
0
    TRACE(("leave newtcpdirect: desthost too long"))
277
0
    goto out;
278
0
  }
279
280
0
  destport = buf_getint(ses.payload);
281
  
282
0
  orighost = buf_getstring(ses.payload, &len);
283
0
  if (len > MAX_HOST_LEN) {
284
0
    TRACE(("leave newtcpdirect: orighost too long"))
285
0
    goto out;
286
0
  }
287
288
0
  origport = buf_getint(ses.payload);
289
290
  /* best be sure */
291
0
  if (origport > 65535 || destport > 65535) {
292
0
    TRACE(("leave newtcpdirect: port > 65535"))
293
0
    goto out;
294
0
  }
295
296
0
  if (!svr_pubkey_allows_local_tcpfwd(desthost, destport)) {
297
0
    TRACE(("leave newtcpdirect: local tcp forwarding not permitted to requested destination"));
298
0
    goto out;
299
0
  }
300
301
0
  snprintf(portstring, sizeof(portstring), "%u", destport);
302
0
  channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done,
303
0
    channel, NULL, NULL, DROPBEAR_PRIO_NORMAL);
304
305
0
  err = SSH_OPEN_IN_PROGRESS;
306
307
0
out:
308
0
  m_free(desthost);
309
0
  m_free(orighost);
310
0
  TRACE(("leave newtcpdirect: err %d", err))
311
0
  return err;
312
0
}
313
314
#endif /* DROPBEAR_SVR_LOCALTCPFWD */
315
316
317
#if DROPBEAR_SVR_LOCALSTREAMFWD
318
319
const struct ChanType svr_chan_streamlocal = {
320
  "direct-streamlocal@openssh.com",
321
  newstreamlocal, /* init */
322
  NULL, /* checkclose */
323
  NULL, /* reqhandler */
324
  NULL, /* closehandler */
325
  NULL /* cleanup */
326
};
327
328
/* Called upon creating a new stream local channel (ie we connect out to an
329
 * address */
330
0
static int newstreamlocal(struct Channel * channel) {
331
332
  /*
333
  https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL#rev1.30
334
335
  byte    SSH_MSG_CHANNEL_OPEN
336
  string    "direct-streamlocal@openssh.com"
337
  uint32    sender channel
338
  uint32    initial window size
339
  uint32    maximum packet size
340
  string    socket path
341
  string    reserved
342
  uint32    reserved
343
  */
344
345
0
  char* destsocket = NULL;
346
0
  unsigned int len;
347
0
  int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
348
349
0
  TRACE(("streamlocal channel %d", channel->index))
350
351
0
  if (svr_opts.forced_command || svr_pubkey_has_forced_command()) {
352
0
    TRACE(("leave newstreamlocal: no unix forwarding for forced command"))
353
0
    goto out;
354
0
  }
355
356
0
  if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) {
357
0
    TRACE(("leave newstreamlocal: local unix forwarding disabled"))
358
0
    goto out;
359
0
  }
360
361
0
  destsocket = buf_getstring(ses.payload, &len);
362
0
  if (len > MAX_HOST_LEN) {
363
0
    TRACE(("leave streamlocal: destsocket too long"))
364
0
    goto out;
365
0
  }
366
367
0
  channel->conn_pending = connect_streamlocal(destsocket, channel_connect_done,
368
0
    channel, DROPBEAR_PRIO_NORMAL);
369
370
0
  err = SSH_OPEN_IN_PROGRESS;
371
372
0
out:
373
  m_free(destsocket);
374
0
  TRACE(("leave streamlocal: err %d", err))
375
0
  return err;
376
0
}
377
378
#endif /* DROPBEAR_SVR_LOCALSTREAMFWD */