/src/dropbear/src/svr-streamfwd.c
Line | Count | Source |
1 | | #include "includes.h" |
2 | | #include "ssh.h" |
3 | | #include "forward.h" |
4 | | #include "dbutil.h" |
5 | | #include "session.h" |
6 | | #include "buffer.h" |
7 | | #include "packet.h" |
8 | | #include "listener.h" |
9 | | #include "runopts.h" |
10 | | #include "auth.h" |
11 | | #include "netio.h" |
12 | | |
13 | | |
14 | | #if DROPBEAR_SVR_REMOTESTREAMFWD |
15 | | static const struct ChanType svr_chan_streamlocalremote = { |
16 | | "forwarded-streamlocal@openssh.com", |
17 | | NULL, |
18 | | NULL, |
19 | | NULL, |
20 | | NULL, |
21 | | NULL |
22 | | }; |
23 | | |
24 | 0 | static int matchstreamlocal(const void* typedata1, const void* typedata2) { |
25 | |
|
26 | 0 | const struct FwdListener *info1 = (const struct FwdListener*)typedata1; |
27 | 0 | const struct FwdListener *info2 = (const struct FwdListener*)typedata2; |
28 | |
|
29 | 0 | if (info1->socket_path == NULL || info2->socket_path == NULL) { |
30 | 0 | return 0; |
31 | 0 | } |
32 | | |
33 | 0 | return (info1->chantype == info2->chantype) |
34 | 0 | && (strcmp(info1->socket_path, info2->socket_path) == 0); |
35 | 0 | } |
36 | | |
37 | 0 | int svr_cancelremotestreamlocal() { |
38 | |
|
39 | 0 | int ret = DROPBEAR_FAILURE; |
40 | 0 | char * socket_path = NULL; |
41 | 0 | unsigned int pathlen; |
42 | 0 | struct Listener * listener = NULL; |
43 | 0 | struct FwdListener tcpinfo; |
44 | |
|
45 | 0 | TRACE(("enter cancelremotestreamlocal")) |
46 | |
|
47 | 0 | socket_path = buf_getstring(ses.payload, &pathlen); |
48 | 0 | if (pathlen > MAX_HOST_LEN) { |
49 | 0 | TRACE(("path len too long: %d", pathlen)) |
50 | 0 | goto out; |
51 | 0 | } |
52 | 0 | if (strlen(socket_path) != pathlen) { |
53 | 0 | TRACE(("path has nul byte")); |
54 | 0 | goto out; |
55 | 0 | } |
56 | | |
57 | 0 | tcpinfo.socket_path = socket_path; |
58 | 0 | tcpinfo.chantype = &svr_chan_streamlocalremote; |
59 | 0 | listener = get_listener(CHANNEL_ID_STREAMLOCALFORWARDED, &tcpinfo, matchstreamlocal); |
60 | 0 | if (listener) { |
61 | 0 | remove_listener( listener ); |
62 | 0 | ret = DROPBEAR_SUCCESS; |
63 | 0 | } |
64 | |
|
65 | 0 | out: |
66 | 0 | m_free(socket_path); |
67 | 0 | TRACE(("leave cancelremotestreamlocal")) |
68 | 0 | return ret; |
69 | 0 | } |
70 | | |
71 | 0 | static void unlink_streamsocket(const char* path) { |
72 | 0 | if (unlink(path) < 0 && errno != ENOENT) { |
73 | | /* Not fatal */ |
74 | 0 | DEBUG1(("Failed removing unix socket %s: %s", |
75 | 0 | path, strerror(errno))); |
76 | 0 | } |
77 | 0 | } |
78 | | |
79 | 0 | static void cleanup_streamlocal(const struct Listener *listener) { |
80 | |
|
81 | 0 | struct FwdListener *tcpinfo = (struct FwdListener*)(listener->typedata); |
82 | |
|
83 | 0 | if (tcpinfo && tcpinfo->socket_path) { |
84 | 0 | unlink_streamsocket(tcpinfo->socket_path); |
85 | 0 | m_free(tcpinfo->socket_path); |
86 | 0 | } |
87 | 0 | m_free(tcpinfo->request_listenaddr); |
88 | 0 | m_free(tcpinfo); |
89 | 0 | } |
90 | | |
91 | 0 | static void streamlocal_acceptor(const struct Listener *listener, int sock) { |
92 | |
|
93 | 0 | int fd; |
94 | 0 | struct FwdListener *tcpinfo = (struct FwdListener*)(listener->typedata); |
95 | |
|
96 | 0 | fd = accept(sock, NULL, NULL); |
97 | 0 | if (fd < 0) { |
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | 0 | if (send_msg_channel_open_init(fd, tcpinfo->chantype) == DROPBEAR_SUCCESS) { |
102 | | /* "forwarded-streamlocal@openssh.com" */ |
103 | | /* socket path that was connected to */ |
104 | 0 | buf_putstring(ses.writepayload, tcpinfo->request_listenaddr, |
105 | 0 | strlen(tcpinfo->request_listenaddr)); |
106 | | /* reserved field */ |
107 | 0 | buf_putstring(ses.writepayload, "", 0); |
108 | |
|
109 | 0 | encrypt_packet(); |
110 | |
|
111 | 0 | } else { |
112 | | /* XXX debug? */ |
113 | 0 | close(fd); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | 0 | int listen_streamlocal(struct FwdListener* tcpinfo, struct Listener **ret_listener) { |
118 | |
|
119 | 0 | int sock, rc, saved_errno; |
120 | 0 | struct Listener *listener = NULL; |
121 | 0 | struct sockaddr_un addr; |
122 | 0 | mode_t old_umask; |
123 | |
|
124 | 0 | TRACE(("enter listen_streamlocal")) |
125 | |
|
126 | 0 | if (tcpinfo->socket_path == NULL) { |
127 | 0 | TRACE(("leave listen_streamlocal: no socket path")) |
128 | 0 | return DROPBEAR_FAILURE; |
129 | 0 | } |
130 | | |
131 | 0 | if (strlen(tcpinfo->socket_path) >= sizeof(addr.sun_path)) { |
132 | 0 | dropbear_log(LOG_INFO, "Streamlocal forward failed: socket path too long"); |
133 | 0 | TRACE(("leave listen_streamlocal: path too long")) |
134 | 0 | return DROPBEAR_FAILURE; |
135 | 0 | } |
136 | | |
137 | 0 | #if DROPBEAR_FUZZ |
138 | 0 | if (fuzz.fuzzing) { |
139 | | // fuzzing streamlocal is unimplemented |
140 | 0 | return DROPBEAR_FAILURE; |
141 | 0 | } |
142 | 0 | #endif |
143 | | |
144 | 0 | sock = socket(PF_UNIX, SOCK_STREAM, 0); |
145 | 0 | if (sock < 0) { |
146 | 0 | dropbear_log(LOG_INFO, "Streamlocal forward failed: socket() failed"); |
147 | 0 | TRACE(("leave listen_streamlocal: socket() failed")) |
148 | 0 | return DROPBEAR_FAILURE; |
149 | 0 | } |
150 | | |
151 | 0 | memset(&addr, 0, sizeof(addr)); |
152 | 0 | addr.sun_family = AF_UNIX; |
153 | 0 | strlcpy(addr.sun_path, tcpinfo->socket_path, sizeof(addr.sun_path)); |
154 | | |
155 | | /* Unlink existing socket if it exists */ |
156 | 0 | unlink_streamsocket(tcpinfo->socket_path); |
157 | | |
158 | | /* Set umask to allow proper permissions on the socket */ |
159 | 0 | old_umask = umask(0177); |
160 | 0 | rc = bind(sock, (struct sockaddr*)&addr, sizeof(addr)); |
161 | 0 | saved_errno = errno; |
162 | 0 | umask(old_umask); |
163 | |
|
164 | 0 | if (rc < 0) { |
165 | 0 | dropbear_log(LOG_INFO, "Streamlocal forward failed: bind() failed: %s", strerror(saved_errno)); |
166 | 0 | m_close(sock); |
167 | 0 | TRACE(("leave listen_streamlocal: bind() failed")) |
168 | 0 | return DROPBEAR_FAILURE; |
169 | 0 | } |
170 | | |
171 | | |
172 | 0 | if (listen(sock, DROPBEAR_LISTEN_BACKLOG) < 0) { |
173 | 0 | dropbear_log(LOG_INFO, "Streamlocal forward failed: listen() failed: %s", strerror(errno)); |
174 | 0 | unlink_streamsocket(tcpinfo->socket_path); |
175 | 0 | m_close(sock); |
176 | 0 | TRACE(("leave listen_streamlocal: listen() failed")) |
177 | 0 | return DROPBEAR_FAILURE; |
178 | 0 | } |
179 | | |
180 | 0 | setnonblocking(sock); |
181 | |
|
182 | 0 | listener = new_listener(&sock, 1, LISTENER_TYPE_STREAMFORWARDED, tcpinfo, |
183 | 0 | streamlocal_acceptor, cleanup_streamlocal); |
184 | |
|
185 | 0 | if (listener == NULL) { |
186 | 0 | unlink_streamsocket(tcpinfo->socket_path); |
187 | 0 | m_close(sock); |
188 | 0 | TRACE(("leave listen_streamlocal: listener failed")) |
189 | 0 | return DROPBEAR_FAILURE; |
190 | 0 | } |
191 | | |
192 | 0 | if (ret_listener) { |
193 | 0 | *ret_listener = listener; |
194 | 0 | } |
195 | |
|
196 | 0 | TRACE(("leave listen_streamlocal: success")) |
197 | 0 | return DROPBEAR_SUCCESS; |
198 | 0 | } |
199 | | |
200 | 0 | int svr_remotestreamlocalreq() { |
201 | |
|
202 | 0 | int ret = DROPBEAR_FAILURE; |
203 | 0 | char * request_path = NULL; |
204 | 0 | unsigned int pathlen; |
205 | 0 | struct FwdListener *tcpinfo = NULL; |
206 | 0 | struct Listener *listener = NULL; |
207 | |
|
208 | 0 | TRACE(("enter remotestreamlocalreq")) |
209 | |
|
210 | 0 | if (svr_opts.forced_command || svr_pubkey_has_forced_command()) { |
211 | | /* Creating a unix socket in the right place could probably subvert |
212 | | * a forcedcommand, so don't allow that. |
213 | | * This could be relaxed if an authorized_keys "permitlisten" |
214 | | * equivalent were added for streamlocal */ |
215 | 0 | TRACE(("leave newstreamlocal: no unix forwarding for forced command")) |
216 | 0 | goto out; |
217 | 0 | } |
218 | | |
219 | 0 | if (svr_opts.noremotefwd || !svr_pubkey_allows_tcpfwd()) { |
220 | 0 | TRACE(("leave remotestreamlocalreq: remote forwarding disabled")) |
221 | 0 | goto out; |
222 | 0 | } |
223 | | |
224 | 0 | request_path = buf_getstring(ses.payload, &pathlen); |
225 | 0 | if (pathlen > MAX_HOST_LEN) { |
226 | 0 | TRACE(("path len too long: %d", pathlen)) |
227 | 0 | goto out; |
228 | 0 | } |
229 | 0 | if (strlen(request_path) != pathlen) { |
230 | 0 | TRACE(("path has nul byte")); |
231 | 0 | goto out; |
232 | 0 | } |
233 | | |
234 | 0 | tcpinfo = (struct FwdListener*)m_malloc(sizeof(struct FwdListener)); |
235 | 0 | memset(tcpinfo, 0, sizeof(struct FwdListener)); |
236 | 0 | tcpinfo->sendaddr = NULL; |
237 | 0 | tcpinfo->sendport = 0; |
238 | 0 | tcpinfo->listenaddr = NULL; |
239 | 0 | tcpinfo->listenport = 0; |
240 | 0 | tcpinfo->chantype = &svr_chan_streamlocalremote; |
241 | 0 | tcpinfo->fwd_type = forwarded; |
242 | 0 | tcpinfo->interface = NULL; |
243 | 0 | tcpinfo->socket_path = m_strdup(request_path); |
244 | 0 | tcpinfo->request_listenaddr = request_path; |
245 | |
|
246 | 0 | ret = listen_streamlocal(tcpinfo, &listener); |
247 | |
|
248 | 0 | out: |
249 | 0 | if (ret == DROPBEAR_FAILURE) { |
250 | | /* we only free it if a listener wasn't created, since the listener |
251 | | * has to remember it if it's to be cancelled */ |
252 | 0 | m_free(request_path); |
253 | 0 | if (tcpinfo) { |
254 | 0 | m_free(tcpinfo->socket_path); |
255 | 0 | } |
256 | 0 | m_free(tcpinfo); |
257 | 0 | } |
258 | |
|
259 | 0 | TRACE(("leave remotestreamlocalreq")) |
260 | 0 | return ret; |
261 | 0 | } |
262 | | #endif /* DROPBEAR_SVR_REMOTESTREAMFWD */ |
263 | | |
264 | | #if DROPBEAR_SVR_LOCALSTREAMFWD |
265 | | |
266 | | /* Called upon creating a new stream local channel (ie we connect out to an |
267 | | * address */ |
268 | 0 | static int newstreamlocal(struct Channel * channel) { |
269 | | |
270 | | /* |
271 | | https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL#rev1.30 |
272 | | |
273 | | byte SSH_MSG_CHANNEL_OPEN |
274 | | string "direct-streamlocal@openssh.com" |
275 | | uint32 sender channel |
276 | | uint32 initial window size |
277 | | uint32 maximum packet size |
278 | | string socket path |
279 | | string reserved |
280 | | uint32 reserved |
281 | | */ |
282 | |
|
283 | 0 | char* destsocket = NULL; |
284 | 0 | unsigned int len; |
285 | 0 | int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; |
286 | |
|
287 | 0 | TRACE(("streamlocal channel %d", channel->index)) |
288 | |
|
289 | 0 | if (svr_opts.forced_command || svr_pubkey_has_forced_command()) { |
290 | 0 | TRACE(("leave newstreamlocal: no unix forwarding for forced command")) |
291 | 0 | goto out; |
292 | 0 | } |
293 | | |
294 | 0 | if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) { |
295 | 0 | TRACE(("leave newstreamlocal: local unix forwarding disabled")) |
296 | 0 | goto out; |
297 | 0 | } |
298 | | |
299 | 0 | destsocket = buf_getstring(ses.payload, &len); |
300 | 0 | if (len > MAX_HOST_LEN) { |
301 | 0 | TRACE(("leave streamlocal: destsocket too long")) |
302 | 0 | goto out; |
303 | 0 | } |
304 | | |
305 | 0 | channel->conn_pending = connect_streamlocal(destsocket, channel_connect_done, |
306 | 0 | channel, DROPBEAR_PRIO_NORMAL); |
307 | |
|
308 | 0 | err = SSH_OPEN_IN_PROGRESS; |
309 | |
|
310 | 0 | out: |
311 | | m_free(destsocket); |
312 | 0 | TRACE(("leave streamlocal: err %d", err)) |
313 | 0 | return err; |
314 | 0 | } |
315 | | |
316 | | const struct ChanType svr_chan_streamlocal = { |
317 | | "direct-streamlocal@openssh.com", |
318 | | newstreamlocal, /* init */ |
319 | | NULL, /* checkclose */ |
320 | | NULL, /* reqhandler */ |
321 | | NULL, /* closehandler */ |
322 | | NULL /* cleanup */ |
323 | | }; |
324 | | |
325 | | #endif /* DROPBEAR_SVR_LOCALSTREAMFWD */ |