/src/dropbear/src/netio.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "netio.h" |
2 | | #include "list.h" |
3 | | #include "dbutil.h" |
4 | | #include "session.h" |
5 | | #include "debug.h" |
6 | | #include "runopts.h" |
7 | | |
8 | | struct dropbear_progress_connection { |
9 | | struct addrinfo *res; |
10 | | struct addrinfo *res_iter; |
11 | | |
12 | | char *remotehost, *remoteport; /* For error reporting */ |
13 | | |
14 | | connect_callback cb; |
15 | | void *cb_data; |
16 | | |
17 | | struct Queue *writequeue; /* A queue of encrypted packets to send with TCP fastopen, |
18 | | or NULL. */ |
19 | | |
20 | | int sock; |
21 | | |
22 | | char* errstring; |
23 | | char *bind_address, *bind_port; |
24 | | enum dropbear_prio prio; |
25 | | }; |
26 | | |
27 | | /* Deallocate a progress connection. Removes from the pending list if iter!=NULL. |
28 | | Does not close sockets */ |
29 | 0 | static void remove_connect(struct dropbear_progress_connection *c, m_list_elem *iter) { |
30 | 0 | if (c->res) { |
31 | | /* Only call freeaddrinfo if connection is not AF_UNIX. */ |
32 | 0 | if (c->res->ai_family != AF_UNIX) { |
33 | 0 | freeaddrinfo(c->res); |
34 | 0 | } else { |
35 | 0 | m_free(c->res); |
36 | 0 | } |
37 | 0 | } |
38 | 0 | m_free(c->remotehost); |
39 | 0 | m_free(c->remoteport); |
40 | 0 | m_free(c->errstring); |
41 | 0 | m_free(c->bind_address); |
42 | 0 | m_free(c->bind_port); |
43 | 0 | m_free(c); |
44 | |
|
45 | 0 | if (iter) { |
46 | 0 | list_remove(iter); |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | 0 | static void cancel_callback(int result, int sock, void* UNUSED(data), const char* UNUSED(errstring)) { |
51 | 0 | if (result == DROPBEAR_SUCCESS) |
52 | 0 | { |
53 | 0 | m_close(sock); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | 0 | void cancel_connect(struct dropbear_progress_connection *c) { |
58 | 0 | c->cb = cancel_callback; |
59 | 0 | c->cb_data = NULL; |
60 | 0 | } |
61 | | |
62 | 0 | static void connect_try_next(struct dropbear_progress_connection *c) { |
63 | 0 | struct addrinfo *r; |
64 | 0 | int err; |
65 | 0 | int res = 0; |
66 | 0 | int fastopen = 0; |
67 | 0 | int retry_errno = EINPROGRESS; |
68 | | #if DROPBEAR_CLIENT_TCP_FAST_OPEN |
69 | | struct msghdr message; |
70 | | #endif |
71 | |
|
72 | 0 | for (r = c->res_iter; r; r = r->ai_next) |
73 | 0 | { |
74 | 0 | dropbear_assert(c->sock == -1); |
75 | | |
76 | 0 | c->sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); |
77 | 0 | if (c->sock < 0) { |
78 | 0 | continue; |
79 | 0 | } |
80 | | |
81 | | /* According to the connect(2) manpage it should be testing EAGAIN |
82 | | * rather than EINPROGRESS for unix sockets. |
83 | | */ |
84 | 0 | retry_errno = r->ai_family == AF_UNIX ? EAGAIN : EINPROGRESS; |
85 | |
|
86 | 0 | if (c->bind_address || c->bind_port) { |
87 | | /* bind to a source port/address */ |
88 | 0 | struct addrinfo hints; |
89 | 0 | struct addrinfo *bindaddr = NULL; |
90 | 0 | memset(&hints, 0, sizeof(hints)); |
91 | 0 | hints.ai_socktype = SOCK_STREAM; |
92 | 0 | hints.ai_family = r->ai_family; |
93 | 0 | hints.ai_flags = AI_PASSIVE; |
94 | |
|
95 | 0 | err = getaddrinfo(c->bind_address, c->bind_port, &hints, &bindaddr); |
96 | 0 | if (err) { |
97 | 0 | int len = 100 + strlen(gai_strerror(err)); |
98 | 0 | m_free(c->errstring); |
99 | 0 | c->errstring = (char*)m_malloc(len); |
100 | 0 | snprintf(c->errstring, len, "Error resolving bind address '%s' (port %s). %s", |
101 | 0 | c->bind_address, c->bind_port, gai_strerror(err)); |
102 | 0 | TRACE(("Error resolving bind: %s", gai_strerror(err))) |
103 | 0 | close(c->sock); |
104 | 0 | c->sock = -1; |
105 | 0 | continue; |
106 | 0 | } |
107 | 0 | res = bind(c->sock, bindaddr->ai_addr, bindaddr->ai_addrlen); |
108 | 0 | freeaddrinfo(bindaddr); |
109 | 0 | bindaddr = NULL; |
110 | 0 | if (res < 0) { |
111 | | /* failure */ |
112 | 0 | int keep_errno = errno; |
113 | 0 | int len = 300; |
114 | 0 | m_free(c->errstring); |
115 | 0 | c->errstring = m_malloc(len); |
116 | 0 | snprintf(c->errstring, len, "Error binding local address '%s' (port %s). %s", |
117 | 0 | c->bind_address, c->bind_port, strerror(keep_errno)); |
118 | 0 | close(c->sock); |
119 | 0 | c->sock = -1; |
120 | 0 | continue; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | 0 | ses.maxfd = MAX(ses.maxfd, c->sock); |
125 | 0 | set_sock_nodelay(c->sock); |
126 | 0 | set_sock_priority(c->sock, c->prio); |
127 | 0 | setnonblocking(c->sock); |
128 | |
|
129 | | #if DROPBEAR_CLIENT_TCP_FAST_OPEN |
130 | | fastopen = (c->writequeue != NULL && r->ai_family != AF_UNIX); |
131 | | |
132 | | if (fastopen) { |
133 | | memset(&message, 0x0, sizeof(message)); |
134 | | message.msg_name = r->ai_addr; |
135 | | message.msg_namelen = r->ai_addrlen; |
136 | | /* 6 is arbitrary, enough to hold initial packets */ |
137 | | unsigned int iovlen = 6; /* Linux msg_iovlen is a size_t */ |
138 | | struct iovec iov[6]; |
139 | | packet_queue_to_iovec(c->writequeue, iov, &iovlen); |
140 | | message.msg_iov = iov; |
141 | | message.msg_iovlen = iovlen; |
142 | | res = sendmsg(c->sock, &message, MSG_FASTOPEN); |
143 | | /* Returns EINPROGRESS if FASTOPEN wasn't available */ |
144 | | if (res < 0) { |
145 | | if (errno != EINPROGRESS) { |
146 | | m_free(c->errstring); |
147 | | c->errstring = m_strdup(strerror(errno)); |
148 | | /* Not entirely sure which kind of errors are normal - 2.6.32 seems to |
149 | | return EPIPE for any (nonblocking?) sendmsg(). just fall back */ |
150 | | TRACE(("sendmsg tcp_fastopen failed, falling back. %s", strerror(errno))); |
151 | | /* No kernel MSG_FASTOPEN support. Fall back below */ |
152 | | fastopen = 0; |
153 | | /* Set to NULL to avoid trying again */ |
154 | | c->writequeue = NULL; |
155 | | } |
156 | | } else { |
157 | | packet_queue_consume(c->writequeue, res); |
158 | | } |
159 | | } |
160 | | #endif |
161 | | |
162 | | /* Normal connect(), used as fallback for TCP fastopen too */ |
163 | 0 | if (!fastopen) { |
164 | 0 | res = connect(c->sock, r->ai_addr, r->ai_addrlen); |
165 | 0 | } |
166 | |
|
167 | 0 | if (res < 0 && errno != retry_errno) { |
168 | | /* failure */ |
169 | 0 | m_free(c->errstring); |
170 | 0 | c->errstring = m_strdup(strerror(errno)); |
171 | 0 | close(c->sock); |
172 | 0 | c->sock = -1; |
173 | 0 | continue; |
174 | 0 | } else { |
175 | | /* new connection was successful, wait for it to complete */ |
176 | 0 | break; |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | 0 | if (r) { |
181 | 0 | c->res_iter = r->ai_next; |
182 | 0 | } else { |
183 | 0 | c->res_iter = NULL; |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | /* Connect via TCP to a host. */ |
188 | | struct dropbear_progress_connection *connect_remote(const char* remotehost, const char* remoteport, |
189 | | connect_callback cb, void* cb_data, |
190 | | const char* bind_address, const char* bind_port, enum dropbear_prio prio) |
191 | 0 | { |
192 | 0 | struct dropbear_progress_connection *c = NULL; |
193 | 0 | int err; |
194 | 0 | struct addrinfo hints; |
195 | |
|
196 | 0 | c = m_malloc(sizeof(*c)); |
197 | 0 | c->remotehost = m_strdup(remotehost); |
198 | 0 | c->remoteport = m_strdup(remoteport); |
199 | 0 | c->sock = -1; |
200 | 0 | c->cb = cb; |
201 | 0 | c->cb_data = cb_data; |
202 | 0 | c->prio = prio; |
203 | |
|
204 | 0 | list_append(&ses.conn_pending, c); |
205 | |
|
206 | 0 | #if DROPBEAR_FUZZ |
207 | 0 | if (fuzz.fuzzing) { |
208 | 0 | c->errstring = m_strdup("fuzzing connect_remote always fails"); |
209 | 0 | return c; |
210 | 0 | } |
211 | 0 | #endif |
212 | | |
213 | 0 | memset(&hints, 0, sizeof(hints)); |
214 | 0 | hints.ai_socktype = SOCK_STREAM; |
215 | 0 | hints.ai_family = AF_UNSPEC; |
216 | |
|
217 | 0 | err = getaddrinfo(remotehost, remoteport, &hints, &c->res); |
218 | 0 | if (err) { |
219 | 0 | int len; |
220 | 0 | len = 100 + strlen(gai_strerror(err)); |
221 | 0 | c->errstring = (char*)m_malloc(len); |
222 | 0 | snprintf(c->errstring, len, "Error resolving '%s' port '%s'. %s", |
223 | 0 | remotehost, remoteport, gai_strerror(err)); |
224 | 0 | TRACE(("Error resolving: %s", gai_strerror(err))) |
225 | 0 | } else { |
226 | 0 | c->res_iter = c->res; |
227 | 0 | } |
228 | | |
229 | 0 | if (bind_address) { |
230 | 0 | c->bind_address = m_strdup(bind_address); |
231 | 0 | } |
232 | 0 | if (bind_port) { |
233 | 0 | c->bind_port = m_strdup(bind_port); |
234 | 0 | } |
235 | |
|
236 | 0 | return c; |
237 | 0 | } |
238 | | |
239 | | |
240 | | /* Connect to stream local socket. */ |
241 | | struct dropbear_progress_connection *connect_streamlocal(const char* localpath, |
242 | | connect_callback cb, void* cb_data, enum dropbear_prio prio) |
243 | 0 | { |
244 | 0 | struct dropbear_progress_connection *c = NULL; |
245 | 0 | struct sockaddr_un *sunaddr; |
246 | |
|
247 | 0 | c = m_malloc(sizeof(*c)); |
248 | 0 | c->remotehost = m_strdup(localpath); |
249 | 0 | c->remoteport = NULL; |
250 | 0 | c->sock = -1; |
251 | 0 | c->cb = cb; |
252 | 0 | c->cb_data = cb_data; |
253 | 0 | c->prio = prio; |
254 | |
|
255 | 0 | list_append(&ses.conn_pending, c); |
256 | |
|
257 | 0 | #if DROPBEAR_FUZZ |
258 | 0 | if (fuzz.fuzzing) { |
259 | 0 | c->errstring = m_strdup("fuzzing connect_streamlocal always fails"); |
260 | 0 | return c; |
261 | 0 | } |
262 | 0 | #endif |
263 | | |
264 | 0 | if (strlen(localpath) >= sizeof(sunaddr->sun_path)) { |
265 | 0 | c->errstring = m_strdup("Stream path too long"); |
266 | 0 | TRACE(("localpath: %s is too long", localpath)); |
267 | 0 | return c; |
268 | 0 | } |
269 | | |
270 | | /* |
271 | | * Fake up a struct addrinfo for AF_UNIX connections. |
272 | | * remove_connect() must check ai_family |
273 | | * and use m_free() not freeaddirinfo() for AF_UNIX. |
274 | | */ |
275 | 0 | c->res = m_malloc(sizeof(*c->res) + sizeof(*sunaddr)); |
276 | 0 | c->res->ai_addr = (struct sockaddr *)(c->res + 1); |
277 | 0 | c->res->ai_addrlen = sizeof(*sunaddr); |
278 | 0 | c->res->ai_family = AF_UNIX; |
279 | 0 | c->res->ai_socktype = SOCK_STREAM; |
280 | 0 | c->res->ai_protocol = PF_UNSPEC; |
281 | 0 | sunaddr = (struct sockaddr_un *)c->res->ai_addr; |
282 | 0 | sunaddr->sun_family = AF_UNIX; |
283 | 0 | strlcpy(sunaddr->sun_path, localpath, sizeof(sunaddr->sun_path)); |
284 | | |
285 | | /* Copy to target iter */ |
286 | 0 | c->res_iter = c->res; |
287 | |
|
288 | 0 | return c; |
289 | 0 | } |
290 | | |
291 | 0 | void remove_connect_pending() { |
292 | 0 | while (ses.conn_pending.first) { |
293 | 0 | struct dropbear_progress_connection *c = ses.conn_pending.first->item; |
294 | 0 | remove_connect(c, ses.conn_pending.first); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | |
299 | 0 | void set_connect_fds(fd_set *writefd) { |
300 | 0 | m_list_elem *iter; |
301 | 0 | iter = ses.conn_pending.first; |
302 | 0 | while (iter) { |
303 | 0 | m_list_elem *next_iter = iter->next; |
304 | 0 | struct dropbear_progress_connection *c = iter->item; |
305 | | /* Set one going */ |
306 | 0 | while (c->res_iter && c->sock < 0) { |
307 | 0 | connect_try_next(c); |
308 | 0 | } |
309 | 0 | if (c->sock >= 0) { |
310 | 0 | FD_SET(c->sock, writefd); |
311 | 0 | } else { |
312 | | /* Final failure */ |
313 | 0 | if (!c->errstring) { |
314 | 0 | c->errstring = m_strdup("unexpected failure"); |
315 | 0 | } |
316 | 0 | c->cb(DROPBEAR_FAILURE, -1, c->cb_data, c->errstring); |
317 | 0 | remove_connect(c, iter); |
318 | 0 | } |
319 | 0 | iter = next_iter; |
320 | 0 | } |
321 | 0 | } |
322 | | |
323 | 0 | void handle_connect_fds(const fd_set *writefd) { |
324 | 0 | m_list_elem *iter; |
325 | 0 | for (iter = ses.conn_pending.first; iter; iter = iter->next) { |
326 | 0 | int val; |
327 | 0 | socklen_t vallen = sizeof(val); |
328 | 0 | struct dropbear_progress_connection *c = iter->item; |
329 | |
|
330 | 0 | if (c->sock < 0 || !FD_ISSET(c->sock, writefd)) { |
331 | 0 | continue; |
332 | 0 | } |
333 | | |
334 | 0 | TRACE(("handling %s port %s socket %d", c->remotehost, c->remoteport, c->sock)); |
335 | |
|
336 | 0 | if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &val, &vallen) != 0) { |
337 | 0 | TRACE(("handle_connect_fds getsockopt(%d) SO_ERROR failed: %s", c->sock, strerror(errno))) |
338 | | /* This isn't expected to happen - Unix has surprises though, continue gracefully. */ |
339 | 0 | m_close(c->sock); |
340 | 0 | c->sock = -1; |
341 | 0 | } else if (val != 0) { |
342 | | /* Connect failed */ |
343 | 0 | TRACE(("connect to %s port %s failed.", c->remotehost, c->remoteport)) |
344 | 0 | m_close(c->sock); |
345 | 0 | c->sock = -1; |
346 | |
|
347 | 0 | m_free(c->errstring); |
348 | 0 | c->errstring = m_strdup(strerror(val)); |
349 | 0 | } else { |
350 | | /* New connection has been established */ |
351 | 0 | c->cb(DROPBEAR_SUCCESS, c->sock, c->cb_data, NULL); |
352 | 0 | remove_connect(c, iter); |
353 | 0 | TRACE(("leave handle_connect_fds - success")) |
354 | | /* Must return here - remove_connect() invalidates iter */ |
355 | 0 | return; |
356 | 0 | } |
357 | 0 | } |
358 | 0 | } |
359 | | |
360 | 0 | void connect_set_writequeue(struct dropbear_progress_connection *c, struct Queue *writequeue) { |
361 | 0 | c->writequeue = writequeue; |
362 | 0 | } |
363 | | |
364 | 0 | void packet_queue_to_iovec(const struct Queue *queue, struct iovec *iov, unsigned int *iov_count) { |
365 | 0 | struct Link *l; |
366 | 0 | unsigned int i; |
367 | 0 | int len; |
368 | 0 | buffer *writebuf; |
369 | |
|
370 | | #ifndef IOV_MAX |
371 | | #if (defined(__CYGWIN__) || defined(__GNU__)) && !defined(UIO_MAXIOV) |
372 | | #define IOV_MAX 1024 |
373 | | #elif defined(__sgi) |
374 | | #define IOV_MAX 512 |
375 | | #else |
376 | | #define IOV_MAX UIO_MAXIOV |
377 | | #endif |
378 | | #endif |
379 | |
|
380 | 0 | *iov_count = MIN(MIN(queue->count, IOV_MAX), *iov_count); |
381 | |
|
382 | 0 | for (l = queue->head, i = 0; i < *iov_count; l = l->link, i++) |
383 | 0 | { |
384 | 0 | writebuf = (buffer*)l->item; |
385 | 0 | len = writebuf->len - writebuf->pos; |
386 | 0 | dropbear_assert(len > 0); |
387 | 0 | TRACE2(("write_packet writev #%d len %d/%d", i, |
388 | 0 | len, writebuf->len)) |
389 | 0 | iov[i].iov_base = buf_getptr(writebuf, len); |
390 | 0 | iov[i].iov_len = len; |
391 | 0 | } |
392 | 0 | } |
393 | | |
394 | 0 | void packet_queue_consume(struct Queue *queue, ssize_t written) { |
395 | 0 | buffer *writebuf; |
396 | 0 | int len; |
397 | 0 | while (written > 0) { |
398 | 0 | writebuf = (buffer*)examine(queue); |
399 | 0 | len = writebuf->len - writebuf->pos; |
400 | 0 | if (len > written) { |
401 | | /* partial buffer write */ |
402 | 0 | buf_incrpos(writebuf, written); |
403 | 0 | written = 0; |
404 | 0 | } else { |
405 | 0 | written -= len; |
406 | 0 | dequeue(queue); |
407 | 0 | buf_free(writebuf); |
408 | 0 | } |
409 | 0 | } |
410 | 0 | } |
411 | | |
412 | 0 | void set_sock_nodelay(int sock) { |
413 | 0 | int val; |
414 | | |
415 | | /* disable nagle */ |
416 | 0 | val = 1; |
417 | 0 | setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&val, sizeof(val)); |
418 | 0 | } |
419 | | |
420 | | #if DROPBEAR_SERVER_TCP_FAST_OPEN |
421 | 0 | void set_listen_fast_open(int sock) { |
422 | 0 | int qlen = MAX(MAX_UNAUTH_PER_IP, 5); |
423 | 0 | if (setsockopt(sock, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)) != 0) { |
424 | 0 | TRACE(("set_listen_fast_open failed for socket %d: %s", sock, strerror(errno))) |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | #endif |
429 | | |
430 | 0 | void set_sock_priority(int sock, enum dropbear_prio prio) { |
431 | |
|
432 | 0 | int rc; |
433 | 0 | int val; |
434 | |
|
435 | 0 | #if DROPBEAR_FUZZ |
436 | 0 | if (fuzz.fuzzing) { |
437 | 0 | TRACE(("fuzzing skips set_sock_prio")) |
438 | 0 | return; |
439 | 0 | } |
440 | 0 | #endif |
441 | | /* Don't log ENOTSOCK errors so that this can harmlessly be called |
442 | | * on a client '-J' proxy pipe */ |
443 | | |
444 | 0 | if (opts.disable_ip_tos == 0) { |
445 | 0 | #ifdef IP_TOS |
446 | | /* Set the DSCP field for outbound IP packet priority. |
447 | | rfc4594 has some guidance to meanings. |
448 | | |
449 | | We set AF21 as "Low-Latency" class for interactive (tty session, |
450 | | also handshake/setup packets). Other traffic is left at the default. |
451 | | |
452 | | OpenSSH at present uses AF21/CS1, rationale |
453 | | https://cvsweb.openbsd.org/src/usr.bin/ssh/readconf.c#rev1.284 |
454 | | |
455 | | Old Dropbear/OpenSSH and Debian/Ubuntu OpenSSH (at Jan 2022) use |
456 | | IPTOS_LOWDELAY/IPTOS_THROUGHPUT |
457 | | |
458 | | DSCP constants are from Linux headers, applicable to other platforms |
459 | | such as macos. |
460 | | */ |
461 | 0 | if (prio == DROPBEAR_PRIO_LOWDELAY) { |
462 | 0 | val = 0x48; /* IPTOS_DSCP_AF21 */ |
463 | 0 | } else { |
464 | 0 | val = 0; /* default */ |
465 | 0 | } |
466 | 0 | #if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS) |
467 | 0 | rc = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, (void*)&val, sizeof(val)); |
468 | 0 | if (rc < 0 && errno != ENOTSOCK) { |
469 | 0 | TRACE(("Couldn't set IPV6_TCLASS (%s)", strerror(errno))); |
470 | 0 | } |
471 | 0 | #endif |
472 | 0 | rc = setsockopt(sock, IPPROTO_IP, IP_TOS, (void*)&val, sizeof(val)); |
473 | 0 | if (rc < 0 && errno != ENOTSOCK) { |
474 | 0 | TRACE(("Couldn't set IP_TOS (%s)", strerror(errno))); |
475 | 0 | } |
476 | 0 | #endif /* IP_TOS */ |
477 | 0 | } |
478 | |
|
479 | 0 | #ifdef HAVE_LINUX_PKT_SCHED_H |
480 | | /* Set scheduling priority within the local Linux network stack */ |
481 | 0 | if (prio == DROPBEAR_PRIO_LOWDELAY) { |
482 | 0 | val = TC_PRIO_INTERACTIVE; |
483 | 0 | } else { |
484 | 0 | val = 0; |
485 | 0 | } |
486 | | /* linux specific, sets QoS class. see tc-prio(8) */ |
487 | 0 | rc = setsockopt(sock, SOL_SOCKET, SO_PRIORITY, (void*) &val, sizeof(val)); |
488 | 0 | if (rc < 0 && errno != ENOTSOCK) { |
489 | 0 | TRACE(("Couldn't set SO_PRIORITY (%s)", strerror(errno))) |
490 | 0 | } |
491 | 0 | #endif |
492 | |
|
493 | 0 | } |
494 | | |
495 | | /* from openssh/canohost.c avoid premature-optimization */ |
496 | 0 | int get_sock_port(int sock) { |
497 | 0 | struct sockaddr_storage from; |
498 | 0 | socklen_t fromlen; |
499 | 0 | char strport[NI_MAXSERV]; |
500 | 0 | int r; |
501 | | |
502 | | /* Get IP address of client. */ |
503 | 0 | fromlen = sizeof(from); |
504 | 0 | memset(&from, 0, sizeof(from)); |
505 | 0 | if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) { |
506 | 0 | TRACE(("getsockname failed: %d", errno)) |
507 | 0 | return 0; |
508 | 0 | } |
509 | | |
510 | | /* Work around Linux IPv6 weirdness */ |
511 | 0 | if (from.ss_family == AF_INET6) |
512 | 0 | fromlen = sizeof(struct sockaddr_in6); |
513 | | |
514 | | /* Non-inet sockets don't have a port number. */ |
515 | 0 | if (from.ss_family != AF_INET && from.ss_family != AF_INET6) |
516 | 0 | return 0; |
517 | | |
518 | | /* Return port number. */ |
519 | 0 | if ((r = getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0, |
520 | 0 | strport, sizeof(strport), NI_NUMERICSERV)) != 0) { |
521 | 0 | TRACE(("netio.c/get_sock_port/getnameinfo NI_NUMERICSERV failed: %d", r)) |
522 | 0 | } |
523 | 0 | return atoi(strport); |
524 | 0 | } |
525 | | |
526 | | /* Listen on address:port. |
527 | | * Special cases are address of "" listening on everything, |
528 | | * and address of NULL listening on localhost only. |
529 | | * Returns the number of sockets bound on success, or -1 on failure. On |
530 | | * failure, if errstring wasn't NULL, it'll be a newly malloced error |
531 | | * string.*/ |
532 | | int dropbear_listen(const char* address, const char* port, |
533 | 0 | int *socks, unsigned int sockcount, char **errstring, int *maxfd, const char* interface) { |
534 | |
|
535 | 0 | struct addrinfo hints, *res = NULL, *res0 = NULL; |
536 | 0 | int err; |
537 | 0 | unsigned int nsock; |
538 | 0 | int val; |
539 | 0 | int sock; |
540 | 0 | uint16_t *allocated_lport_p = NULL; |
541 | 0 | int allocated_lport = 0; |
542 | | |
543 | 0 | TRACE(("enter dropbear_listen")) |
544 | |
|
545 | 0 | #if DROPBEAR_FUZZ |
546 | 0 | if (fuzz.fuzzing) { |
547 | 0 | return fuzz_dropbear_listen(address, port, socks, sockcount, errstring, maxfd); |
548 | 0 | } |
549 | 0 | #endif |
550 | | |
551 | 0 | memset(&hints, 0, sizeof(hints)); |
552 | 0 | hints.ai_family = AF_UNSPEC; /* TODO: let them flag v4 only etc */ |
553 | 0 | hints.ai_socktype = SOCK_STREAM; |
554 | | |
555 | | /* for calling getaddrinfo: |
556 | | address == NULL and !AI_PASSIVE: local loopback |
557 | | address == NULL and AI_PASSIVE: all interfaces |
558 | | address != NULL: whatever the address says */ |
559 | 0 | if (!address) { |
560 | 0 | TRACE(("dropbear_listen: local loopback")) |
561 | 0 | } else { |
562 | 0 | if (address[0] == '\0') { |
563 | 0 | if (interface) { |
564 | 0 | TRACE(("dropbear_listen: %s", interface)) |
565 | 0 | } else { |
566 | 0 | TRACE(("dropbear_listen: all interfaces")) |
567 | 0 | } |
568 | 0 | address = NULL; |
569 | 0 | } |
570 | 0 | hints.ai_flags = AI_PASSIVE; |
571 | 0 | } |
572 | 0 | err = getaddrinfo(address, port, &hints, &res0); |
573 | |
|
574 | 0 | if (err) { |
575 | 0 | if (errstring != NULL && *errstring == NULL) { |
576 | 0 | int len; |
577 | 0 | len = 20 + strlen(gai_strerror(err)); |
578 | 0 | *errstring = (char*)m_malloc(len); |
579 | 0 | snprintf(*errstring, len, "Error resolving: %s", gai_strerror(err)); |
580 | 0 | } |
581 | 0 | if (res0) { |
582 | 0 | freeaddrinfo(res0); |
583 | 0 | res0 = NULL; |
584 | 0 | } |
585 | 0 | TRACE(("leave dropbear_listen: failed resolving")) |
586 | 0 | return -1; |
587 | 0 | } |
588 | | |
589 | | /* When listening on server-assigned-port 0 |
590 | | * the assigned ports may differ for address families (v4/v6) |
591 | | * causing problems for tcpip-forward. |
592 | | * Caller can do a get_socket_address to discover assigned-port |
593 | | * hence, use same port for all address families */ |
594 | 0 | allocated_lport = 0; |
595 | 0 | nsock = 0; |
596 | 0 | for (res = res0; res != NULL && nsock < sockcount; |
597 | 0 | res = res->ai_next) { |
598 | 0 | if (allocated_lport > 0) { |
599 | 0 | if (AF_INET == res->ai_family) { |
600 | 0 | allocated_lport_p = &((struct sockaddr_in *)res->ai_addr)->sin_port; |
601 | 0 | } else if (AF_INET6 == res->ai_family) { |
602 | 0 | allocated_lport_p = &((struct sockaddr_in6 *)res->ai_addr)->sin6_port; |
603 | 0 | } |
604 | 0 | *allocated_lport_p = htons(allocated_lport); |
605 | 0 | } |
606 | | |
607 | | /* Get a socket */ |
608 | 0 | socks[nsock] = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
609 | 0 | sock = socks[nsock]; /* For clarity */ |
610 | 0 | if (sock < 0) { |
611 | 0 | err = errno; |
612 | 0 | TRACE(("socket() failed")) |
613 | 0 | continue; |
614 | 0 | } |
615 | | |
616 | | /* Various useful socket options */ |
617 | 0 | val = 1; |
618 | | /* set to reuse, quick timeout */ |
619 | 0 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &val, sizeof(val)); |
620 | |
|
621 | 0 | #ifdef SO_BINDTODEVICE |
622 | 0 | if(interface && setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface, strlen(interface)) < 0) { |
623 | 0 | dropbear_log(LOG_WARNING, "Couldn't set SO_BINDTODEVICE"); |
624 | 0 | TRACE(("Failed setsockopt with errno failure, %d %s", errno, strerror(errno))) |
625 | 0 | } |
626 | 0 | #endif |
627 | |
|
628 | 0 | #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) |
629 | 0 | if (res->ai_family == AF_INET6) { |
630 | 0 | int on = 1; |
631 | 0 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, |
632 | 0 | &on, sizeof(on)) == -1) { |
633 | 0 | dropbear_log(LOG_WARNING, "Couldn't set IPV6_V6ONLY"); |
634 | 0 | } |
635 | 0 | } |
636 | 0 | #endif |
637 | 0 | set_sock_nodelay(sock); |
638 | |
|
639 | 0 | if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { |
640 | 0 | err = errno; |
641 | 0 | close(sock); |
642 | 0 | TRACE(("bind(%s) failed", port)) |
643 | 0 | continue; |
644 | 0 | } |
645 | | |
646 | 0 | if (listen(sock, DROPBEAR_LISTEN_BACKLOG) < 0) { |
647 | 0 | err = errno; |
648 | 0 | close(sock); |
649 | 0 | TRACE(("listen() failed")) |
650 | 0 | continue; |
651 | 0 | } |
652 | | |
653 | 0 | if (0 == allocated_lport) { |
654 | 0 | allocated_lport = get_sock_port(sock); |
655 | 0 | } |
656 | |
|
657 | 0 | *maxfd = MAX(*maxfd, sock); |
658 | 0 | nsock++; |
659 | 0 | } |
660 | |
|
661 | 0 | if (res0) { |
662 | 0 | freeaddrinfo(res0); |
663 | 0 | res0 = NULL; |
664 | 0 | } |
665 | |
|
666 | 0 | if (nsock == 0) { |
667 | 0 | if (errstring != NULL && *errstring == NULL) { |
668 | 0 | int len; |
669 | 0 | len = 20 + strlen(strerror(err)); |
670 | 0 | *errstring = (char*)m_malloc(len); |
671 | 0 | snprintf(*errstring, len, "Error listening: %s", strerror(err)); |
672 | 0 | } |
673 | 0 | TRACE(("leave dropbear_listen: failure, %s", strerror(err))) |
674 | 0 | return -1; |
675 | 0 | } |
676 | | |
677 | 0 | TRACE(("leave dropbear_listen: success, %d socks bound", nsock)) |
678 | 0 | return nsock; |
679 | 0 | } |
680 | | |
681 | | void get_socket_address(int fd, char **local_host, char **local_port, |
682 | | char **remote_host, char **remote_port, int host_lookup) |
683 | 0 | { |
684 | 0 | struct sockaddr_storage addr; |
685 | 0 | socklen_t addrlen; |
686 | |
|
687 | 0 | #if DROPBEAR_FUZZ |
688 | 0 | if (fuzz.fuzzing) { |
689 | 0 | fuzz_get_socket_address(fd, local_host, local_port, remote_host, remote_port, host_lookup); |
690 | 0 | return; |
691 | 0 | } |
692 | 0 | #endif |
693 | | |
694 | 0 | if (local_host || local_port) { |
695 | 0 | addrlen = sizeof(addr); |
696 | 0 | if (getsockname(fd, (struct sockaddr*)&addr, &addrlen) < 0) { |
697 | 0 | dropbear_exit("Failed socket address: %s", strerror(errno)); |
698 | 0 | } |
699 | 0 | getaddrstring(&addr, local_host, local_port, host_lookup); |
700 | 0 | } |
701 | 0 | if (remote_host || remote_port) { |
702 | 0 | addrlen = sizeof(addr); |
703 | 0 | if (getpeername(fd, (struct sockaddr*)&addr, &addrlen) < 0) { |
704 | 0 | dropbear_exit("Failed socket address: %s", strerror(errno)); |
705 | 0 | } |
706 | 0 | getaddrstring(&addr, remote_host, remote_port, host_lookup); |
707 | 0 | } |
708 | 0 | } |
709 | | |
710 | | /* Return a string representation of the socket address passed. The return |
711 | | * value is allocated with malloc() */ |
712 | | void getaddrstring(struct sockaddr_storage* addr, |
713 | | char **ret_host, char **ret_port, |
714 | 0 | int host_lookup) { |
715 | |
|
716 | 0 | char host[NI_MAXHOST+1], serv[NI_MAXSERV+1]; |
717 | 0 | unsigned int len; |
718 | 0 | int ret; |
719 | | |
720 | 0 | int flags = NI_NUMERICSERV | NI_NUMERICHOST; |
721 | |
|
722 | 0 | #if !DO_HOST_LOOKUP |
723 | 0 | host_lookup = 0; |
724 | 0 | #endif |
725 | | |
726 | 0 | if (host_lookup) { |
727 | 0 | flags = NI_NUMERICSERV; |
728 | 0 | } |
729 | |
|
730 | 0 | len = sizeof(struct sockaddr_storage); |
731 | | /* Some platforms such as Solaris 8 require that len is the length |
732 | | * of the specific structure. Some older linux systems (glibc 2.1.3 |
733 | | * such as debian potato) have sockaddr_storage.__ss_family instead |
734 | | * but we'll ignore them */ |
735 | 0 | #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY |
736 | 0 | if (addr->ss_family == AF_INET) { |
737 | 0 | len = sizeof(struct sockaddr_in); |
738 | 0 | } |
739 | 0 | #ifdef AF_INET6 |
740 | 0 | if (addr->ss_family == AF_INET6) { |
741 | 0 | len = sizeof(struct sockaddr_in6); |
742 | 0 | } |
743 | 0 | #endif |
744 | 0 | #endif |
745 | |
|
746 | 0 | ret = getnameinfo((struct sockaddr*)addr, len, host, sizeof(host)-1, |
747 | 0 | serv, sizeof(serv)-1, flags); |
748 | |
|
749 | 0 | if (ret != 0) { |
750 | 0 | if (host_lookup) { |
751 | | /* On some systems (Darwin does it) we get EINTR from getnameinfo |
752 | | * somehow. Eew. So we'll just return the IP, since that doesn't seem |
753 | | * to exhibit that behaviour. */ |
754 | 0 | getaddrstring(addr, ret_host, ret_port, 0); |
755 | 0 | return; |
756 | 0 | } else { |
757 | | /* if we can't do a numeric lookup, something's gone terribly wrong */ |
758 | 0 | dropbear_exit("Failed lookup: %s", gai_strerror(ret)); |
759 | 0 | } |
760 | 0 | } |
761 | | |
762 | 0 | if (ret_host) { |
763 | 0 | *ret_host = m_strdup(host); |
764 | 0 | } |
765 | 0 | if (ret_port) { |
766 | 0 | *ret_port = m_strdup(serv); |
767 | 0 | } |
768 | 0 | } |
769 | | |