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