1 /*
2 * Dropbear SSH
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */
24
25 #include "includes.h"
26 #include "dbutil.h"
27 #include "tcpfwd.h"
28 #include "channel.h"
29 #include "runopts.h"
30 #include "session.h"
31 #include "ssh.h"
32 #include "netio.h"
33
34 #if DROPBEAR_CLI_REMOTETCPFWD
35 static int newtcpforwarded(struct Channel * channel);
36
37 const struct ChanType cli_chan_tcpremote = {
38 1, /* sepfds */
39 "forwarded-tcpip",
40 newtcpforwarded,
41 NULL,
42 NULL,
43 NULL,
44 NULL
45 };
46 #endif
47
48 #if DROPBEAR_CLI_LOCALTCPFWD
49 static int cli_localtcp(const char* listenaddr,
50 unsigned int listenport,
51 const char* remoteaddr,
52 unsigned int remoteport);
53 static const struct ChanType cli_chan_tcplocal = {
54 1, /* sepfds */
55 "direct-tcpip",
56 tcp_prio_inithandler,
57 NULL,
58 NULL,
59 NULL,
60 NULL
61 };
62 #endif
63
64 #if DROPBEAR_CLI_ANYTCPFWD
65 static void fwd_failed(const char* format, ...) ATTRIB_PRINTF(1,2);
fwd_failed(const char * format,...)66 static void fwd_failed(const char* format, ...)
67 {
68 va_list param;
69 va_start(param, format);
70
71 if (cli_opts.exit_on_fwd_failure) {
72 _dropbear_exit(EXIT_FAILURE, format, param);
73 } else {
74 _dropbear_log(LOG_WARNING, format, param);
75 }
76
77 va_end(param);
78 }
79 #endif
80
81 #if DROPBEAR_CLI_LOCALTCPFWD
setup_localtcp()82 void setup_localtcp() {
83 m_list_elem *iter;
84 int ret;
85
86 TRACE(("enter setup_localtcp"))
87
88 for (iter = cli_opts.localfwds->first; iter; iter = iter->next) {
89 struct TCPFwdEntry * fwd = (struct TCPFwdEntry*)iter->item;
90 ret = cli_localtcp(
91 fwd->listenaddr,
92 fwd->listenport,
93 fwd->connectaddr,
94 fwd->connectport);
95 if (ret == DROPBEAR_FAILURE) {
96 fwd_failed("Failed local port forward %s:%d:%s:%d",
97 fwd->listenaddr,
98 fwd->listenport,
99 fwd->connectaddr,
100 fwd->connectport);
101 }
102 }
103 TRACE(("leave setup_localtcp"))
104
105 }
106
cli_localtcp(const char * listenaddr,unsigned int listenport,const char * remoteaddr,unsigned int remoteport)107 static int cli_localtcp(const char* listenaddr,
108 unsigned int listenport,
109 const char* remoteaddr,
110 unsigned int remoteport) {
111
112 struct TCPListener* tcpinfo = NULL;
113 int ret;
114
115 TRACE(("enter cli_localtcp: %d %s %d", listenport, remoteaddr,
116 remoteport));
117
118 tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener));
119
120 tcpinfo->sendaddr = m_strdup(remoteaddr);
121 tcpinfo->sendport = remoteport;
122
123 if (listenaddr)
124 {
125 tcpinfo->listenaddr = m_strdup(listenaddr);
126 }
127 else
128 {
129 if (opts.listen_fwd_all) {
130 tcpinfo->listenaddr = m_strdup("");
131 } else {
132 tcpinfo->listenaddr = m_strdup("localhost");
133 }
134 }
135 tcpinfo->listenport = listenport;
136
137 tcpinfo->chantype = &cli_chan_tcplocal;
138 tcpinfo->tcp_type = direct;
139
140 ret = listen_tcpfwd(tcpinfo, NULL);
141
142 if (ret == DROPBEAR_FAILURE) {
143 m_free(tcpinfo);
144 }
145 TRACE(("leave cli_localtcp: %d", ret))
146 return ret;
147 }
148 #endif /* DROPBEAR_CLI_LOCALTCPFWD */
149
150 #if DROPBEAR_CLI_REMOTETCPFWD
send_msg_global_request_remotetcp(const char * addr,int port)151 static void send_msg_global_request_remotetcp(const char *addr, int port) {
152
153 TRACE(("enter send_msg_global_request_remotetcp"))
154
155 CHECKCLEARTOWRITE();
156 buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST);
157 buf_putstring(ses.writepayload, "tcpip-forward", 13);
158 buf_putbyte(ses.writepayload, 1); /* want_reply */
159 buf_putstring(ses.writepayload, addr, strlen(addr));
160 buf_putint(ses.writepayload, port);
161
162 encrypt_packet();
163
164 TRACE(("leave send_msg_global_request_remotetcp"))
165 }
166
167 /* The only global success/failure messages are for remotetcp.
168 * Since there isn't any identifier in these messages, we have to rely on them
169 * being in the same order as we sent the requests. This is the ordering
170 * of the cli_opts.remotefwds list.
171 * If the requested remote port is 0 the listen port will be
172 * dynamically allocated by the server and the port number will be returned
173 * to client and the port number reported to the user. */
cli_recv_msg_request_success()174 void cli_recv_msg_request_success() {
175 /* We just mark off that we have received the reply,
176 * so that we can report failure for later ones. */
177 m_list_elem * iter = NULL;
178 for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) {
179 struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item;
180 if (!fwd->have_reply) {
181 fwd->have_reply = 1;
182 if (fwd->listenport == 0) {
183 /* The server should let us know which port was allocated if we requested port 0 */
184 int allocport = buf_getint(ses.payload);
185 if (allocport > 0) {
186 fwd->listenport = allocport;
187 dropbear_log(LOG_INFO, "Allocated port %d for remote forward to %s:%d",
188 allocport, fwd->connectaddr, fwd->connectport);
189 }
190 }
191 return;
192 }
193 }
194 }
195
cli_recv_msg_request_failure()196 void cli_recv_msg_request_failure() {
197 m_list_elem *iter;
198 for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) {
199 struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item;
200 if (!fwd->have_reply) {
201 fwd->have_reply = 1;
202 fwd_failed("Remote TCP forward request failed (port %d -> %s:%d)",
203 fwd->listenport,
204 fwd->connectaddr,
205 fwd->connectport);
206 return;
207 }
208 }
209 }
210
setup_remotetcp()211 void setup_remotetcp() {
212 m_list_elem *iter;
213 TRACE(("enter setup_remotetcp"))
214
215 for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) {
216 struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item;
217 if (!fwd->listenaddr)
218 {
219 /* we store the addresses so that we can compare them
220 when the server sends them back */
221 if (opts.listen_fwd_all) {
222 fwd->listenaddr = m_strdup("");
223 } else {
224 fwd->listenaddr = m_strdup("localhost");
225 }
226 }
227 send_msg_global_request_remotetcp(fwd->listenaddr, fwd->listenport);
228 }
229
230 TRACE(("leave setup_remotetcp"))
231 }
232
newtcpforwarded(struct Channel * channel)233 static int newtcpforwarded(struct Channel * channel) {
234
235 char *origaddr = NULL;
236 unsigned int origport;
237 m_list_elem * iter = NULL;
238 struct TCPFwdEntry *fwd = NULL;
239 char portstring[NI_MAXSERV];
240 int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
241
242 origaddr = buf_getstring(ses.payload, NULL);
243 origport = buf_getint(ses.payload);
244
245 /* Find which port corresponds. First try and match address as well as port,
246 in case they want to forward different ports separately ... */
247 for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) {
248 fwd = (struct TCPFwdEntry*)iter->item;
249 if (origport == fwd->listenport
250 && strcmp(origaddr, fwd->listenaddr) == 0) {
251 break;
252 }
253 }
254
255 if (!iter)
256 {
257 /* ... otherwise try to generically match the only forwarded port
258 without address (also handles ::1 vs 127.0.0.1 vs localhost case).
259 rfc4254 is vague about the definition of "address that was connected" */
260 for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) {
261 fwd = (struct TCPFwdEntry*)iter->item;
262 if (origport == fwd->listenport) {
263 break;
264 }
265 }
266 }
267
268
269 if (iter == NULL || fwd == NULL) {
270 /* We didn't request forwarding on that port */
271 cleantext(origaddr);
272 dropbear_log(LOG_INFO, "Server sent unrequested forward from \"%s:%d\"",
273 origaddr, origport);
274 goto out;
275 }
276
277 snprintf(portstring, sizeof(portstring), "%u", fwd->connectport);
278 channel->conn_pending = connect_remote(fwd->connectaddr, portstring, channel_connect_done, channel, NULL, NULL);
279
280 channel->prio = DROPBEAR_CHANNEL_PRIO_UNKNOWABLE;
281
282 err = SSH_OPEN_IN_PROGRESS;
283
284 out:
285 m_free(origaddr);
286 TRACE(("leave newtcpdirect: err %d", err))
287 return err;
288 }
289 #endif /* DROPBEAR_CLI_REMOTETCPFWD */
290