Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Low-level socket handling |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1998,2000 Michael R. Elkins <me@mutt.org> |
7 | | * Copyright (C) 1999-2006,2008 Brendan Cully <brendan@kublai.com> |
8 | | * Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi> |
9 | | * Copyright (C) 2017 Damien Riegel <damien.riegel@gmail.com> |
10 | | * |
11 | | * @copyright |
12 | | * This program is free software: you can redistribute it and/or modify it under |
13 | | * the terms of the GNU General Public License as published by the Free Software |
14 | | * Foundation, either version 2 of the License, or (at your option) any later |
15 | | * version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
20 | | * details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License along with |
23 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
24 | | */ |
25 | | |
26 | | /** |
27 | | * @page conn_raw Low-level socket code |
28 | | * |
29 | | * Low-level socket handling |
30 | | */ |
31 | | |
32 | | #include "config.h" |
33 | | #include <errno.h> |
34 | | #include <fcntl.h> |
35 | | #include <netdb.h> |
36 | | #include <netinet/in.h> |
37 | | #include <signal.h> |
38 | | #include <stdint.h> |
39 | | #include <stdio.h> |
40 | | #include <string.h> |
41 | | #include <sys/select.h> |
42 | | #include <sys/socket.h> |
43 | | #include <unistd.h> |
44 | | #include "private.h" |
45 | | #include "mutt/lib.h" |
46 | | #include "config/lib.h" |
47 | | #include "core/lib.h" |
48 | | #include "gui/lib.h" |
49 | | #include "lib.h" |
50 | | #include "globals.h" |
51 | | #ifdef HAVE_LIBIDN |
52 | | #include "address/lib.h" |
53 | | #endif |
54 | | #ifdef HAVE_GETADDRINFO |
55 | | #include <stdbool.h> |
56 | | #endif |
57 | | |
58 | | /** |
59 | | * socket_connect - Set up to connect to a socket fd |
60 | | * @param fd File descriptor to connect with |
61 | | * @param sa Address info |
62 | | * @retval 0 Success |
63 | | * @retval >0 An errno, e.g. EPERM |
64 | | * @retval -1 Error |
65 | | */ |
66 | | static int socket_connect(int fd, struct sockaddr *sa) |
67 | 0 | { |
68 | 0 | int sa_size; |
69 | 0 | int save_errno; |
70 | 0 | sigset_t set; |
71 | |
|
72 | 0 | if (sa->sa_family == AF_INET) |
73 | 0 | sa_size = sizeof(struct sockaddr_in); |
74 | 0 | #ifdef HAVE_GETADDRINFO |
75 | 0 | else if (sa->sa_family == AF_INET6) |
76 | 0 | sa_size = sizeof(struct sockaddr_in6); |
77 | 0 | #endif |
78 | 0 | else |
79 | 0 | { |
80 | 0 | mutt_debug(LL_DEBUG1, "Unknown address family!\n"); |
81 | 0 | return -1; |
82 | 0 | } |
83 | | |
84 | 0 | const short c_socket_timeout = cs_subset_number(NeoMutt->sub, "socket_timeout"); |
85 | 0 | if (c_socket_timeout > 0) |
86 | 0 | alarm(c_socket_timeout); |
87 | |
|
88 | 0 | mutt_sig_allow_interrupt(true); |
89 | | |
90 | | /* FreeBSD's connect() does not respect SA_RESTART, meaning |
91 | | * a SIGWINCH will cause the connect to fail. */ |
92 | 0 | sigemptyset(&set); |
93 | 0 | sigaddset(&set, SIGWINCH); |
94 | 0 | sigprocmask(SIG_BLOCK, &set, NULL); |
95 | |
|
96 | 0 | save_errno = 0; |
97 | |
|
98 | 0 | if (c_socket_timeout > 0) |
99 | 0 | { |
100 | 0 | const struct timeval tv = { c_socket_timeout, 0 }; |
101 | 0 | if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) |
102 | 0 | { |
103 | 0 | mutt_debug(LL_DEBUG2, "Cannot set socket receive timeout. errno: %d\n", errno); |
104 | 0 | } |
105 | 0 | if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) |
106 | 0 | { |
107 | 0 | mutt_debug(LL_DEBUG2, "Cannot set socket send timeout. errno: %d\n", errno); |
108 | 0 | } |
109 | 0 | } |
110 | |
|
111 | 0 | if (connect(fd, sa, sa_size) < 0) |
112 | 0 | { |
113 | 0 | save_errno = errno; |
114 | 0 | mutt_debug(LL_DEBUG2, "Connection failed. errno: %d\n", errno); |
115 | 0 | SigInt = false; /* reset in case we caught SIGINTR while in connect() */ |
116 | 0 | } |
117 | |
|
118 | 0 | if (c_socket_timeout > 0) |
119 | 0 | alarm(0); |
120 | 0 | mutt_sig_allow_interrupt(false); |
121 | 0 | sigprocmask(SIG_UNBLOCK, &set, NULL); |
122 | |
|
123 | 0 | return save_errno; |
124 | 0 | } |
125 | | |
126 | | /** |
127 | | * raw_socket_open - Open a socket - Implements Connection::open() - @ingroup connection_open |
128 | | */ |
129 | | int raw_socket_open(struct Connection *conn) |
130 | 0 | { |
131 | 0 | int rc; |
132 | |
|
133 | 0 | char *host_idna = NULL; |
134 | |
|
135 | 0 | #ifdef HAVE_GETADDRINFO |
136 | | /* --- IPv4/6 --- */ |
137 | | |
138 | | /* "65536\0" */ |
139 | 0 | char port[6] = { 0 }; |
140 | 0 | struct addrinfo hints; |
141 | 0 | struct addrinfo *res = NULL; |
142 | 0 | struct addrinfo *cur = NULL; |
143 | | |
144 | | /* we accept v4 or v6 STREAM sockets */ |
145 | 0 | memset(&hints, 0, sizeof(hints)); |
146 | |
|
147 | 0 | const bool c_use_ipv6 = cs_subset_bool(NeoMutt->sub, "use_ipv6"); |
148 | 0 | if (c_use_ipv6) |
149 | 0 | hints.ai_family = AF_UNSPEC; |
150 | 0 | else |
151 | 0 | hints.ai_family = AF_INET; |
152 | |
|
153 | 0 | hints.ai_socktype = SOCK_STREAM; |
154 | |
|
155 | 0 | snprintf(port, sizeof(port), "%d", conn->account.port); |
156 | |
|
157 | 0 | #ifdef HAVE_LIBIDN |
158 | 0 | if (mutt_idna_to_ascii_lz(conn->account.host, &host_idna, 1) != 0) |
159 | 0 | { |
160 | 0 | mutt_error(_("Bad IDN: '%s'"), conn->account.host); |
161 | 0 | return -1; |
162 | 0 | } |
163 | | #else |
164 | | host_idna = conn->account.host; |
165 | | #endif |
166 | | |
167 | 0 | if (!OptNoCurses) |
168 | 0 | mutt_message(_("Looking up %s..."), conn->account.host); |
169 | |
|
170 | 0 | rc = getaddrinfo(host_idna, port, &hints, &res); |
171 | |
|
172 | 0 | #ifdef HAVE_LIBIDN |
173 | 0 | FREE(&host_idna); |
174 | 0 | #endif |
175 | |
|
176 | 0 | if (rc) |
177 | 0 | { |
178 | 0 | mutt_error(_("Could not find the host \"%s\""), conn->account.host); |
179 | 0 | return -1; |
180 | 0 | } |
181 | | |
182 | 0 | if (!OptNoCurses) |
183 | 0 | mutt_message(_("Connecting to %s..."), conn->account.host); |
184 | |
|
185 | 0 | rc = -1; |
186 | 0 | for (cur = res; cur; cur = cur->ai_next) |
187 | 0 | { |
188 | 0 | int fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); |
189 | 0 | if (fd >= 0) |
190 | 0 | { |
191 | 0 | rc = socket_connect(fd, cur->ai_addr); |
192 | 0 | if (rc == 0) |
193 | 0 | { |
194 | 0 | (void) fcntl(fd, F_SETFD, FD_CLOEXEC); |
195 | 0 | conn->fd = fd; |
196 | 0 | break; |
197 | 0 | } |
198 | 0 | else |
199 | 0 | { |
200 | 0 | close(fd); |
201 | 0 | } |
202 | 0 | } |
203 | 0 | } |
204 | |
|
205 | 0 | freeaddrinfo(res); |
206 | | #else |
207 | | /* --- IPv4 only --- */ |
208 | | |
209 | | struct sockaddr_in sin; |
210 | | struct hostent *he = NULL; |
211 | | |
212 | | memset(&sin, 0, sizeof(sin)); |
213 | | sin.sin_port = htons(conn->account.port); |
214 | | sin.sin_family = AF_INET; |
215 | | |
216 | | #ifdef HAVE_LIBIDN |
217 | | if (mutt_idna_to_ascii_lz(conn->account.host, &host_idna, 1) != 0) |
218 | | { |
219 | | mutt_error(_("Bad IDN: '%s'"), conn->account.host); |
220 | | return -1; |
221 | | } |
222 | | #else |
223 | | host_idna = conn->account.host; |
224 | | #endif |
225 | | |
226 | | if (!OptNoCurses) |
227 | | mutt_message(_("Looking up %s..."), conn->account.host); |
228 | | |
229 | | he = gethostbyname(host_idna); |
230 | | |
231 | | #ifdef HAVE_LIBIDN |
232 | | FREE(&host_idna); |
233 | | #endif |
234 | | |
235 | | if (!he) |
236 | | { |
237 | | mutt_error(_("Could not find the host \"%s\""), conn->account.host); |
238 | | |
239 | | return -1; |
240 | | } |
241 | | |
242 | | if (!OptNoCurses) |
243 | | mutt_message(_("Connecting to %s..."), conn->account.host); |
244 | | |
245 | | rc = -1; |
246 | | for (int i = 0; he->h_addr_list[i]; i++) |
247 | | { |
248 | | memcpy(&sin.sin_addr, he->h_addr_list[i], he->h_length); |
249 | | int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); |
250 | | |
251 | | if (fd >= 0) |
252 | | { |
253 | | rc = socket_connect(fd, (struct sockaddr *) &sin); |
254 | | if (rc == 0) |
255 | | { |
256 | | fcntl(fd, F_SETFD, FD_CLOEXEC); |
257 | | conn->fd = fd; |
258 | | break; |
259 | | } |
260 | | else |
261 | | { |
262 | | close(fd); |
263 | | } |
264 | | } |
265 | | } |
266 | | #endif |
267 | 0 | if (rc) |
268 | 0 | { |
269 | 0 | mutt_error(_("Could not connect to %s (%s)"), conn->account.host, |
270 | 0 | (rc > 0) ? strerror(rc) : _("unknown error")); |
271 | 0 | return -1; |
272 | 0 | } |
273 | | |
274 | 0 | return 0; |
275 | 0 | } |
276 | | |
277 | | /** |
278 | | * raw_socket_read - Read data from a socket - Implements Connection::read() - @ingroup connection_read |
279 | | */ |
280 | | int raw_socket_read(struct Connection *conn, char *buf, size_t count) |
281 | 0 | { |
282 | 0 | int rc; |
283 | |
|
284 | 0 | mutt_sig_allow_interrupt(true); |
285 | 0 | do |
286 | 0 | { |
287 | 0 | rc = read(conn->fd, buf, count); |
288 | 0 | } while (rc < 0 && (errno == EINTR)); |
289 | |
|
290 | 0 | if (rc < 0) |
291 | 0 | { |
292 | 0 | mutt_error(_("Error talking to %s (%s)"), conn->account.host, strerror(errno)); |
293 | 0 | SigInt = false; |
294 | 0 | } |
295 | 0 | mutt_sig_allow_interrupt(false); |
296 | |
|
297 | 0 | if (SigInt) |
298 | 0 | { |
299 | 0 | mutt_error(_("Connection to %s has been aborted"), conn->account.host); |
300 | 0 | SigInt = false; |
301 | 0 | rc = -1; |
302 | 0 | } |
303 | |
|
304 | 0 | return rc; |
305 | 0 | } |
306 | | |
307 | | /** |
308 | | * raw_socket_write - Write data to a socket - Implements Connection::write() - @ingroup connection_write |
309 | | */ |
310 | | int raw_socket_write(struct Connection *conn, const char *buf, size_t count) |
311 | 0 | { |
312 | 0 | int rc; |
313 | 0 | size_t sent = 0; |
314 | |
|
315 | 0 | mutt_sig_allow_interrupt(true); |
316 | 0 | do |
317 | 0 | { |
318 | 0 | do |
319 | 0 | { |
320 | 0 | rc = write(conn->fd, buf + sent, count - sent); |
321 | 0 | } while (rc < 0 && (errno == EINTR)); |
322 | |
|
323 | 0 | if (rc < 0) |
324 | 0 | { |
325 | 0 | mutt_error(_("Error talking to %s (%s)"), conn->account.host, strerror(errno)); |
326 | 0 | mutt_sig_allow_interrupt(false); |
327 | 0 | return -1; |
328 | 0 | } |
329 | | |
330 | 0 | sent += rc; |
331 | 0 | } while ((sent < count) && !SigInt); |
332 | | |
333 | 0 | mutt_sig_allow_interrupt(false); |
334 | 0 | return sent; |
335 | 0 | } |
336 | | |
337 | | /** |
338 | | * raw_socket_poll - Checks whether reads would block - Implements Connection::poll() - @ingroup connection_poll |
339 | | */ |
340 | | int raw_socket_poll(struct Connection *conn, time_t wait_secs) |
341 | 0 | { |
342 | 0 | if (conn->fd < 0) |
343 | 0 | return -1; |
344 | | |
345 | 0 | fd_set rfds; |
346 | 0 | struct timeval tv; |
347 | |
|
348 | 0 | uint64_t wait_millis = wait_secs * 1000UL; |
349 | |
|
350 | 0 | while (true) |
351 | 0 | { |
352 | 0 | tv.tv_sec = wait_millis / 1000; |
353 | 0 | tv.tv_usec = (wait_millis % 1000) * 1000; |
354 | |
|
355 | 0 | FD_ZERO(&rfds); |
356 | 0 | FD_SET(conn->fd, &rfds); |
357 | |
|
358 | 0 | uint64_t pre_t = mutt_date_now_ms(); |
359 | 0 | const int rc = select(conn->fd + 1, &rfds, NULL, NULL, &tv); |
360 | 0 | uint64_t post_t = mutt_date_now_ms(); |
361 | |
|
362 | 0 | if ((rc > 0) || ((rc < 0) && (errno != EINTR))) |
363 | 0 | return rc; |
364 | | |
365 | 0 | if (SigInt) |
366 | 0 | mutt_query_exit(); |
367 | |
|
368 | 0 | wait_millis += pre_t; |
369 | 0 | if (wait_millis <= post_t) |
370 | 0 | return 0; |
371 | 0 | wait_millis -= post_t; |
372 | 0 | } |
373 | 0 | } |
374 | | |
375 | | /** |
376 | | * raw_socket_close - Close a socket - Implements Connection::close() - @ingroup connection_close |
377 | | */ |
378 | | int raw_socket_close(struct Connection *conn) |
379 | 0 | { |
380 | 0 | return close(conn->fd); |
381 | 0 | } |