Coverage Report

Created: 2025-10-10 06:20

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
static int newstreamlocal(struct Channel * channel);
60
61
#if DROPBEAR_SVR_REMOTETCPFWD
62
static const struct ChanType svr_chan_tcpremote = {
63
  "forwarded-tcpip",
64
  NULL,
65
  NULL,
66
  NULL,
67
  NULL,
68
  NULL
69
};
70
71
/* At the moment this is completely used for tcp code (with the name reflecting
72
 * that). If new request types are added, this should be replaced with code
73
 * similar to the request-switching in chansession.c */
74
0
void recv_msg_global_request_remotetcp() {
75
76
0
  char* reqname = NULL;
77
0
  unsigned int namelen;
78
0
  unsigned int wantreply = 0;
79
0
  int ret = DROPBEAR_FAILURE;
80
81
0
  TRACE(("enter recv_msg_global_request_remotetcp"))
82
83
0
  reqname = buf_getstring(ses.payload, &namelen);
84
0
  wantreply = buf_getbool(ses.payload);
85
86
0
  if (svr_opts.noremotetcp || !svr_pubkey_allows_tcpfwd()) {
87
0
    TRACE(("leave recv_msg_global_request_remotetcp: remote tcp forwarding disabled"))
88
0
    goto out;
89
0
  }
90
91
0
  if (namelen > MAX_NAME_LEN) {
92
0
    TRACE(("name len is wrong: %d", namelen))
93
0
    goto out;
94
0
  }
95
96
0
  if (strcmp("tcpip-forward", reqname) == 0) {
97
0
    int allocated_listen_port = 0;
98
0
    ret = svr_remotetcpreq(&allocated_listen_port);
99
    /* client expects-port-number-to-make-use-of-server-allocated-ports */
100
0
    if (DROPBEAR_SUCCESS == ret) {
101
0
      CHECKCLEARTOWRITE();
102
0
      buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS);
103
0
      buf_putint(ses.writepayload, allocated_listen_port);
104
0
      encrypt_packet();
105
0
      wantreply = 0; /* avoid out: below sending another reply */
106
0
    }
107
0
  } else if (strcmp("cancel-tcpip-forward", reqname) == 0) {
108
0
    ret = svr_cancelremotetcp();
109
0
  } else {
110
0
    TRACE(("reqname isn't tcpip-forward: '%s'", reqname))
111
0
  }
112
113
0
out:
114
0
  if (wantreply) {
115
0
    if (ret == DROPBEAR_SUCCESS) {
116
0
      send_msg_request_success();
117
0
    } else {
118
0
      send_msg_request_failure();
119
0
    }
120
0
  }
121
122
0
  m_free(reqname);
123
124
0
  TRACE(("leave recv_msg_global_request"))
125
0
}
126
127
0
static int matchtcp(const void* typedata1, const void* typedata2) {
128
129
0
  const struct TCPListener *info1 = (struct TCPListener*)typedata1;
130
0
  const struct TCPListener *info2 = (struct TCPListener*)typedata2;
131
132
0
  return (info1->listenport == info2->listenport)
133
0
      && (info1->chantype == info2->chantype)
134
0
      && (strcmp(info1->listenaddr, info2->listenaddr) == 0);
135
0
}
136
137
0
static int svr_cancelremotetcp() {
138
139
0
  int ret = DROPBEAR_FAILURE;
140
0
  char * bindaddr = NULL;
141
0
  unsigned int addrlen;
142
0
  unsigned int port;
143
0
  struct Listener * listener = NULL;
144
0
  struct TCPListener tcpinfo;
145
146
0
  TRACE(("enter cancelremotetcp"))
147
148
0
  bindaddr = buf_getstring(ses.payload, &addrlen);
149
0
  if (addrlen > MAX_HOST_LEN) {
150
0
    TRACE(("addr len too long: %d", addrlen))
151
0
    goto out;
152
0
  }
153
154
0
  port = buf_getint(ses.payload);
155
156
0
  tcpinfo.sendaddr = NULL;
157
0
  tcpinfo.sendport = 0;
158
0
  tcpinfo.listenaddr = bindaddr;
159
0
  tcpinfo.listenport = port;
160
0
  listener = get_listener(CHANNEL_ID_TCPFORWARDED, &tcpinfo, matchtcp);
161
0
  if (listener) {
162
0
    remove_listener( listener );
163
0
    ret = DROPBEAR_SUCCESS;
164
0
  }
165
166
0
out:
167
0
  m_free(bindaddr);
168
0
  TRACE(("leave cancelremotetcp"))
169
0
  return ret;
170
0
}
171
172
0
static int svr_remotetcpreq(int *allocated_listen_port) {
173
174
0
  int ret = DROPBEAR_FAILURE;
175
0
  char * request_addr = NULL;
176
0
  unsigned int addrlen;
177
0
  struct TCPListener *tcpinfo = NULL;
178
0
  unsigned int port;
179
0
  struct Listener *listener = NULL;
180
181
0
  TRACE(("enter remotetcpreq"))
182
183
0
  request_addr = buf_getstring(ses.payload, &addrlen);
184
0
  if (addrlen > MAX_HOST_LEN) {
185
0
    TRACE(("addr len too long: %d", addrlen))
186
0
    goto out;
187
0
  }
188
189
0
  port = buf_getint(ses.payload);
190
191
0
  if (port != 0) {
192
0
    if (port < 1 || port > 65535) {
193
0
      TRACE(("invalid port: %d", port))
194
0
      goto out;
195
0
    }
196
197
0
    if (!ses.allowprivport && port < IPPORT_RESERVED) {
198
0
      TRACE(("can't assign port < 1024 for non-root"))
199
0
      goto out;
200
0
    }
201
0
  }
202
203
0
  tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener));
204
0
  tcpinfo->sendaddr = NULL;
205
0
  tcpinfo->sendport = 0;
206
0
  tcpinfo->listenport = port;
207
0
  tcpinfo->chantype = &svr_chan_tcpremote;
208
0
  tcpinfo->tcp_type = forwarded;
209
0
  tcpinfo->interface = svr_opts.interface;
210
211
0
  tcpinfo->request_listenaddr = request_addr;
212
0
  if (!opts.listen_fwd_all || (strcmp(request_addr, "localhost") == 0) ) {
213
    /* NULL means "localhost only" */
214
0
    tcpinfo->listenaddr = NULL;
215
0
  }
216
0
  else
217
0
  {
218
0
    tcpinfo->listenaddr = m_strdup(request_addr);
219
0
  }
220
221
0
  ret = listen_tcpfwd(tcpinfo, &listener);
222
0
  if (DROPBEAR_SUCCESS == ret) {
223
0
    tcpinfo->listenport = get_sock_port(listener->socks[0]);
224
0
    *allocated_listen_port = tcpinfo->listenport;
225
0
  }
226
227
0
out:
228
0
  if (ret == DROPBEAR_FAILURE) {
229
    /* we only free it if a listener wasn't created, since the listener
230
     * has to remember it if it's to be cancelled */
231
0
    m_free(request_addr);
232
0
    m_free(tcpinfo);
233
0
  }
234
235
0
  TRACE(("leave remotetcpreq"))
236
237
0
  return ret;
238
0
}
239
240
#endif /* DROPBEAR_SVR_REMOTETCPFWD */
241
242
#if DROPBEAR_SVR_LOCALTCPFWD
243
244
const struct ChanType svr_chan_tcpdirect = {
245
  "direct-tcpip",
246
  newtcpdirect, /* init */
247
  NULL, /* checkclose */
248
  NULL, /* reqhandler */
249
  NULL, /* closehandler */
250
  NULL /* cleanup */
251
};
252
253
/* Called upon creating a new direct tcp channel (ie we connect out to an
254
 * address */
255
0
static int newtcpdirect(struct Channel * channel) {
256
257
0
  char* desthost = NULL;
258
0
  unsigned int destport;
259
0
  char* orighost = NULL;
260
0
  unsigned int origport;
261
0
  char portstring[NI_MAXSERV];
262
0
  unsigned int len;
263
0
  int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
264
265
0
  TRACE(("newtcpdirect channel %d", channel->index))
266
267
0
  if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) {
268
0
    TRACE(("leave newtcpdirect: local tcp forwarding disabled"))
269
0
    goto out;
270
0
  }
271
272
0
  desthost = buf_getstring(ses.payload, &len);
273
0
  if (len > MAX_HOST_LEN) {
274
0
    TRACE(("leave newtcpdirect: desthost too long"))
275
0
    goto out;
276
0
  }
277
278
0
  destport = buf_getint(ses.payload);
279
  
280
0
  orighost = buf_getstring(ses.payload, &len);
281
0
  if (len > MAX_HOST_LEN) {
282
0
    TRACE(("leave newtcpdirect: orighost too long"))
283
0
    goto out;
284
0
  }
285
286
0
  origport = buf_getint(ses.payload);
287
288
  /* best be sure */
289
0
  if (origport > 65535 || destport > 65535) {
290
0
    TRACE(("leave newtcpdirect: port > 65535"))
291
0
    goto out;
292
0
  }
293
294
0
  if (!svr_pubkey_allows_local_tcpfwd(desthost, destport)) {
295
0
    TRACE(("leave newtcpdirect: local tcp forwarding not permitted to requested destination"));
296
0
    goto out;
297
0
  }
298
299
0
  snprintf(portstring, sizeof(portstring), "%u", destport);
300
0
  channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done,
301
0
    channel, NULL, NULL, DROPBEAR_PRIO_NORMAL);
302
303
0
  err = SSH_OPEN_IN_PROGRESS;
304
305
0
out:
306
0
  m_free(desthost);
307
0
  m_free(orighost);
308
0
  TRACE(("leave newtcpdirect: err %d", err))
309
0
  return err;
310
0
}
311
312
#endif /* DROPBEAR_SVR_LOCALTCPFWD */
313
314
315
#if DROPBEAR_SVR_LOCALSTREAMFWD
316
317
const struct ChanType svr_chan_streamlocal = {
318
  "direct-streamlocal@openssh.com",
319
  newstreamlocal, /* init */
320
  NULL, /* checkclose */
321
  NULL, /* reqhandler */
322
  NULL, /* closehandler */
323
  NULL /* cleanup */
324
};
325
326
/* Called upon creating a new stream local channel (ie we connect out to an
327
 * address */
328
0
static int newstreamlocal(struct Channel * channel) {
329
330
  /*
331
  https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL#rev1.30
332
333
  byte    SSH_MSG_CHANNEL_OPEN
334
  string    "direct-streamlocal@openssh.com"
335
  uint32    sender channel
336
  uint32    initial window size
337
  uint32    maximum packet size
338
  string    socket path
339
  string    reserved
340
  uint32    reserved
341
  */
342
343
0
  char* destsocket = NULL;
344
0
  unsigned int len;
345
0
  int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
346
347
0
  TRACE(("streamlocal channel %d", channel->index))
348
349
0
  if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) {
350
0
    TRACE(("leave newstreamlocal: local unix forwarding disabled"))
351
0
    goto out;
352
0
  }
353
354
0
  destsocket = buf_getstring(ses.payload, &len);
355
0
  if (len > MAX_HOST_LEN) {
356
0
    TRACE(("leave streamlocal: destsocket too long"))
357
0
    goto out;
358
0
  }
359
360
0
  channel->conn_pending = connect_streamlocal(destsocket, channel_connect_done,
361
0
    channel, DROPBEAR_PRIO_NORMAL);
362
363
0
  err = SSH_OPEN_IN_PROGRESS;
364
365
0
out:
366
  m_free(destsocket);
367
0
  TRACE(("leave streamlocal: err %d", err))
368
0
  return err;
369
0
}
370
371
#endif /* DROPBEAR_SVR_LOCALSTREAMFWD */