/src/dropbear/src/cli-tcpfwd.c
Line | Count | Source (jump to first uncovered line) |
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 | | "forwarded-tcpip", |
39 | | newtcpforwarded, |
40 | | NULL, |
41 | | NULL, |
42 | | NULL, |
43 | | NULL |
44 | | }; |
45 | | #endif |
46 | | |
47 | | #if DROPBEAR_CLI_LOCALTCPFWD |
48 | | static int cli_localtcp(const char* listenaddr, |
49 | | unsigned int listenport, |
50 | | const char* remoteaddr, |
51 | | unsigned int remoteport); |
52 | | static const struct ChanType cli_chan_tcplocal = { |
53 | | "direct-tcpip", |
54 | | NULL, |
55 | | NULL, |
56 | | NULL, |
57 | | NULL, |
58 | | NULL |
59 | | }; |
60 | | #endif |
61 | | |
62 | | #if DROPBEAR_CLI_ANYTCPFWD |
63 | | static void fwd_failed(const char* format, ...) ATTRIB_PRINTF(1,2); |
64 | | static void fwd_failed(const char* format, ...) |
65 | 0 | { |
66 | 0 | va_list param; |
67 | 0 | va_start(param, format); |
68 | |
|
69 | 0 | if (cli_opts.exit_on_fwd_failure) { |
70 | 0 | _dropbear_exit(EXIT_FAILURE, format, param); |
71 | 0 | } else { |
72 | 0 | _dropbear_log(LOG_WARNING, format, param); |
73 | 0 | } |
74 | | |
75 | 0 | va_end(param); |
76 | 0 | } |
77 | | #endif |
78 | | |
79 | | #if DROPBEAR_CLI_LOCALTCPFWD |
80 | 0 | void setup_localtcp() { |
81 | 0 | m_list_elem *iter; |
82 | 0 | int ret; |
83 | |
|
84 | 0 | TRACE(("enter setup_localtcp")) |
85 | |
|
86 | 0 | for (iter = cli_opts.localfwds->first; iter; iter = iter->next) { |
87 | 0 | struct TCPFwdEntry * fwd = (struct TCPFwdEntry*)iter->item; |
88 | 0 | ret = cli_localtcp( |
89 | 0 | fwd->listenaddr, |
90 | 0 | fwd->listenport, |
91 | 0 | fwd->connectaddr, |
92 | 0 | fwd->connectport); |
93 | 0 | if (ret == DROPBEAR_FAILURE) { |
94 | 0 | fwd_failed("Failed local port forward %s:%d:%s:%d", |
95 | 0 | fwd->listenaddr, |
96 | 0 | fwd->listenport, |
97 | 0 | fwd->connectaddr, |
98 | 0 | fwd->connectport); |
99 | 0 | } |
100 | 0 | } |
101 | 0 | TRACE(("leave setup_localtcp")) |
102 | |
|
103 | 0 | } |
104 | | |
105 | | static int cli_localtcp(const char* listenaddr, |
106 | | unsigned int listenport, |
107 | | const char* remoteaddr, |
108 | 0 | unsigned int remoteport) { |
109 | |
|
110 | 0 | struct TCPListener* tcpinfo = NULL; |
111 | 0 | int ret; |
112 | |
|
113 | 0 | TRACE(("enter cli_localtcp: %d %s %d", listenport, remoteaddr, |
114 | 0 | remoteport)); |
115 | |
|
116 | 0 | tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener)); |
117 | |
|
118 | 0 | tcpinfo->sendaddr = m_strdup(remoteaddr); |
119 | 0 | tcpinfo->sendport = remoteport; |
120 | |
|
121 | 0 | if (listenaddr) |
122 | 0 | { |
123 | 0 | tcpinfo->listenaddr = m_strdup(listenaddr); |
124 | 0 | } |
125 | 0 | else |
126 | 0 | { |
127 | 0 | if (opts.listen_fwd_all) { |
128 | 0 | tcpinfo->listenaddr = m_strdup(""); |
129 | 0 | } else { |
130 | 0 | tcpinfo->listenaddr = m_strdup("localhost"); |
131 | 0 | } |
132 | 0 | } |
133 | 0 | tcpinfo->listenport = listenport; |
134 | |
|
135 | 0 | tcpinfo->chantype = &cli_chan_tcplocal; |
136 | 0 | tcpinfo->tcp_type = direct; |
137 | |
|
138 | 0 | ret = listen_tcpfwd(tcpinfo, NULL); |
139 | |
|
140 | 0 | if (ret == DROPBEAR_FAILURE) { |
141 | 0 | m_free(tcpinfo); |
142 | 0 | } |
143 | 0 | TRACE(("leave cli_localtcp: %d", ret)) |
144 | 0 | return ret; |
145 | 0 | } |
146 | | #endif /* DROPBEAR_CLI_LOCALTCPFWD */ |
147 | | |
148 | | #if DROPBEAR_CLI_REMOTETCPFWD |
149 | 0 | static void send_msg_global_request_remotetcp(const char *addr, int port) { |
150 | |
|
151 | 0 | TRACE(("enter send_msg_global_request_remotetcp")) |
152 | |
|
153 | 0 | CHECKCLEARTOWRITE(); |
154 | 0 | buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST); |
155 | 0 | buf_putstring(ses.writepayload, "tcpip-forward", 13); |
156 | 0 | buf_putbyte(ses.writepayload, 1); /* want_reply */ |
157 | 0 | buf_putstring(ses.writepayload, addr, strlen(addr)); |
158 | 0 | buf_putint(ses.writepayload, port); |
159 | |
|
160 | 0 | encrypt_packet(); |
161 | |
|
162 | 0 | TRACE(("leave send_msg_global_request_remotetcp")) |
163 | 0 | } |
164 | | |
165 | | /* The only global success/failure messages are for remotetcp. |
166 | | * Since there isn't any identifier in these messages, we have to rely on them |
167 | | * being in the same order as we sent the requests. This is the ordering |
168 | | * of the cli_opts.remotefwds list. |
169 | | * If the requested remote port is 0 the listen port will be |
170 | | * dynamically allocated by the server and the port number will be returned |
171 | | * to client and the port number reported to the user. */ |
172 | 225 | void cli_recv_msg_request_success() { |
173 | | /* We just mark off that we have received the reply, |
174 | | * so that we can report failure for later ones. */ |
175 | 225 | m_list_elem * iter = NULL; |
176 | 225 | for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { |
177 | 0 | struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; |
178 | 0 | if (!fwd->have_reply) { |
179 | 0 | fwd->have_reply = 1; |
180 | 0 | if (fwd->listenport == 0) { |
181 | | /* The server should let us know which port was allocated if we requested port 0 */ |
182 | 0 | int allocport = buf_getint(ses.payload); |
183 | 0 | if (allocport > 0) { |
184 | 0 | fwd->listenport = allocport; |
185 | 0 | dropbear_log(LOG_INFO, "Allocated port %d for remote forward to %s:%d", |
186 | 0 | allocport, fwd->connectaddr, fwd->connectport); |
187 | 0 | } |
188 | 0 | } |
189 | 0 | return; |
190 | 0 | } |
191 | 0 | } |
192 | 225 | } |
193 | | |
194 | 197 | void cli_recv_msg_request_failure() { |
195 | 197 | m_list_elem *iter; |
196 | 197 | for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { |
197 | 0 | struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; |
198 | 0 | if (!fwd->have_reply) { |
199 | 0 | fwd->have_reply = 1; |
200 | 0 | fwd_failed("Remote TCP forward request failed (port %d -> %s:%d)", |
201 | 0 | fwd->listenport, |
202 | 0 | fwd->connectaddr, |
203 | 0 | fwd->connectport); |
204 | 0 | return; |
205 | 0 | } |
206 | 0 | } |
207 | 197 | } |
208 | | |
209 | 0 | void setup_remotetcp() { |
210 | 0 | m_list_elem *iter; |
211 | 0 | TRACE(("enter setup_remotetcp")) |
212 | |
|
213 | 0 | for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { |
214 | 0 | struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; |
215 | 0 | if (!fwd->listenaddr) |
216 | 0 | { |
217 | | /* we store the addresses so that we can compare them |
218 | | when the server sends them back */ |
219 | 0 | if (opts.listen_fwd_all) { |
220 | 0 | fwd->listenaddr = m_strdup(""); |
221 | 0 | } else { |
222 | 0 | fwd->listenaddr = m_strdup("localhost"); |
223 | 0 | } |
224 | 0 | } |
225 | 0 | send_msg_global_request_remotetcp(fwd->listenaddr, fwd->listenport); |
226 | 0 | } |
227 | |
|
228 | 0 | TRACE(("leave setup_remotetcp")) |
229 | 0 | } |
230 | | |
231 | 197 | static int newtcpforwarded(struct Channel * channel) { |
232 | | |
233 | 197 | char *origaddr = NULL; |
234 | 197 | unsigned int origport; |
235 | 197 | m_list_elem * iter = NULL; |
236 | 197 | struct TCPFwdEntry *fwd = NULL; |
237 | 197 | char portstring[NI_MAXSERV]; |
238 | 197 | int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; |
239 | | |
240 | 197 | origaddr = buf_getstring(ses.payload, NULL); |
241 | 197 | origport = buf_getint(ses.payload); |
242 | | |
243 | | /* Find which port corresponds. First try and match address as well as port, |
244 | | in case they want to forward different ports separately ... */ |
245 | 197 | for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { |
246 | 0 | fwd = (struct TCPFwdEntry*)iter->item; |
247 | 0 | if (origport == fwd->listenport |
248 | 0 | && strcmp(origaddr, fwd->listenaddr) == 0) { |
249 | 0 | break; |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | 197 | if (!iter) |
254 | 195 | { |
255 | | /* ... otherwise try to generically match the only forwarded port |
256 | | without address (also handles ::1 vs 127.0.0.1 vs localhost case). |
257 | | rfc4254 is vague about the definition of "address that was connected" */ |
258 | 195 | for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { |
259 | 0 | fwd = (struct TCPFwdEntry*)iter->item; |
260 | 0 | if (origport == fwd->listenport) { |
261 | 0 | break; |
262 | 0 | } |
263 | 0 | } |
264 | 195 | } |
265 | | |
266 | | |
267 | 197 | if (iter == NULL || fwd == NULL) { |
268 | | /* We didn't request forwarding on that port */ |
269 | 195 | cleantext(origaddr); |
270 | 195 | dropbear_log(LOG_INFO, "Server sent unrequested forward from \"%s:%d\"", |
271 | 195 | origaddr, origport); |
272 | 195 | goto out; |
273 | 195 | } |
274 | | |
275 | 2 | snprintf(portstring, sizeof(portstring), "%u", fwd->connectport); |
276 | 2 | channel->conn_pending = connect_remote(fwd->connectaddr, portstring, channel_connect_done, |
277 | 2 | channel, NULL, NULL, DROPBEAR_PRIO_NORMAL); |
278 | | |
279 | 2 | err = SSH_OPEN_IN_PROGRESS; |
280 | | |
281 | 195 | out: |
282 | 195 | m_free(origaddr); |
283 | 195 | TRACE(("leave newtcpdirect: err %d", err)) |
284 | 195 | return err; |
285 | 2 | } |
286 | | #endif /* DROPBEAR_CLI_REMOTETCPFWD */ |