Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/conn/raw.c
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
}