drop openssl support as well, only use GNUTLS.
[apps/madmutt.git] / lib-sys / mutt_socket.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1998 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2005 Brendan Cully <brendan@kublai.com>
5  * Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #include <lib-lib/lib-lib.h>
13
14 #include <netinet/in.h>
15 #include <netdb.h>
16 #include <sys/socket.h>
17
18 #include <lib-ui/curses.h>
19
20 #include "mutt.h"
21 #include "globals.h"
22
23 #include "unix.h"
24 #include "mutt_socket.h"
25 #include "mutt_tunnel.h"
26 #include "mutt_signal.h"
27
28 #ifdef HAVE_LIBIDN
29 #include <idna.h>
30 #endif
31 #include "mutt_idna.h"
32
33 /* support for multiple socket connections */
34 static CONNECTION *Connections = NULL;
35
36 /* forward declarations */
37 static int socket_preconnect (void);
38 static int socket_connect (int fd, struct sockaddr *sa);
39 static CONNECTION *socket_new_conn (void);
40
41 /* Wrappers */
42 int mutt_socket_open (CONNECTION * conn)
43 {
44   if (socket_preconnect ())
45     return -1;
46
47   return conn->conn_open (conn);
48 }
49
50 int mutt_socket_close (CONNECTION * conn)
51 {
52   int rc = -1;
53
54   if (conn->fd >= 0)
55     rc = conn->conn_close (conn);
56
57   conn->fd = -1;
58   conn->ssf = 0;
59
60   return rc;
61 }
62
63 int mutt_socket_read (CONNECTION * conn, char *buf, ssize_t len)
64 {
65   int rc;
66
67   if (conn->fd < 0) {
68     return -1;
69   }
70
71   rc = conn->conn_read (conn, buf, len);
72   /* EOF */
73   if (rc == 0) {
74     mutt_error (_("Connection to %s closed"), conn->account.host);
75     mutt_sleep (2);
76   }
77   if (rc <= 0)
78     mutt_socket_close (conn);
79
80   return rc;
81 }
82
83 int mutt_socket_write(CONNECTION * conn, const char *buf)
84 {
85   int rc;
86   int len;
87
88   if (conn->fd < 0) {
89     return -1;
90   }
91
92   len = m_strlen(buf);
93   if ((rc = conn->conn_write (conn, buf, len)) < 0) {
94     mutt_socket_close (conn);
95
96     return -1;
97   }
98
99   return rc;
100 }
101
102 /* simple read buffering to speed things up. */
103 int mutt_socket_readchar (CONNECTION * conn, char *c)
104 {
105   if (conn->bufpos >= conn->available) {
106     if (conn->fd >= 0)
107       conn->available =
108         conn->conn_read (conn, conn->inbuf, sizeof (conn->inbuf));
109     else {
110       return -1;
111     }
112     conn->bufpos = 0;
113     if (conn->available == 0) {
114       mutt_error (_("Connection to %s closed"), conn->account.host);
115       mutt_sleep (2);
116     }
117     if (conn->available <= 0) {
118       mutt_socket_close (conn);
119       return -1;
120     }
121   }
122   *c = conn->inbuf[conn->bufpos];
123   conn->bufpos++;
124   return 1;
125 }
126
127 int mutt_socket_readln(char *buf, ssize_t buflen, CONNECTION * conn)
128 {
129   char ch;
130   ssize_t i;
131
132   for (i = 0; i < buflen - 1; i++) {
133     if (mutt_socket_readchar (conn, &ch) != 1) {
134       buf[i] = '\0';
135       return -1;
136     }
137
138     if (ch == '\n')
139       break;
140     buf[i] = ch;
141   }
142
143   /* strip \r from \r\n termination */
144   if (i && buf[i - 1] == '\r')
145     buf[--i] = '\0';
146   else
147     buf[i] = '\0';
148
149   /* number of bytes read, not m_strlen*/
150   return i + 1;
151 }
152
153 CONNECTION *mutt_socket_head (void)
154 {
155   return Connections;
156 }
157
158 /* mutt_socket_free: remove connection from connection list and free it */
159 void mutt_socket_free (CONNECTION * conn)
160 {
161   CONNECTION *iter;
162   CONNECTION *tmp;
163
164   iter = Connections;
165
166   /* head is special case, doesn't need prev updated */
167   if (iter == conn) {
168     Connections = iter->next;
169     p_delete(&iter);
170     return;
171   }
172
173   while (iter->next) {
174     if (iter->next == conn) {
175       tmp = iter->next;
176       iter->next = tmp->next;
177       p_delete(&tmp);
178       return;
179     }
180     iter = iter->next;
181   }
182 }
183
184 /* mutt_conn_find: find a connection off the list of connections whose
185  *   account matches account. If start is not null, only search for
186  *   connections after the given connection (allows higher level socket code
187  *   to make more fine-grained searches than account info - eg in IMAP we may
188  *   wish to find a connection which is not in IMAP_SELECTED state) */
189 CONNECTION *mutt_conn_find (const CONNECTION * start, const ACCOUNT * account)
190 {
191   CONNECTION *conn;
192   ciss_url_t url;
193   char hook[LONG_STRING];
194
195   /* account isn't actually modified, since url isn't either */
196   mutt_account_tourl ((ACCOUNT *) account, &url);
197   url.path = NULL;
198   url_ciss_tostring (&url, hook, sizeof (hook), 0);
199   mutt_account_hook (hook);
200
201   conn = start ? start->next : Connections;
202   while (conn) {
203     if (mutt_account_match (account, &(conn->account)))
204       return conn;
205     conn = conn->next;
206   }
207
208   conn = socket_new_conn ();
209   memcpy (&conn->account, account, sizeof (ACCOUNT));
210
211   conn->next = Connections;
212   Connections = conn;
213
214   if (Tunnel && *Tunnel)
215     mutt_tunnel_socket_setup (conn);
216   else if (account->flags & M_ACCT_SSL) {
217     if (mutt_ssl_socket_setup (conn) < 0) {
218       mutt_socket_free (conn);
219       return NULL;
220     }
221   } else {
222     conn->conn_read = raw_socket_read;
223     conn->conn_write = raw_socket_write;
224     conn->conn_open = raw_socket_open;
225     conn->conn_close = raw_socket_close;
226   }
227
228   return conn;
229 }
230
231 static int socket_preconnect (void)
232 {
233   int rc;
234   int save_errno;
235
236   if (m_strlen(Preconnect)) {
237     rc = mutt_system (Preconnect);
238     if (rc) {
239       save_errno = errno;
240       mutt_perror (_("Preconnect command failed."));
241       mutt_sleep (1);
242
243       return save_errno;
244     }
245   }
246
247   return 0;
248 }
249
250 /* socket_connect: set up to connect to a socket fd. */
251 static int socket_connect (int fd, struct sockaddr *sa)
252 {
253   int sa_size;
254   int save_errno;
255
256   if (sa->sa_family == AF_INET)
257     sa_size = sizeof (struct sockaddr_in);
258 #ifdef HAVE_GETADDRINFO
259   else if (sa->sa_family == AF_INET6)
260     sa_size = sizeof (struct sockaddr_in6);
261 #endif
262   else {
263     return -1;
264   }
265
266   if (ConnectTimeout > 0)
267     alarm (ConnectTimeout);
268
269   mutt_allow_interrupt (1);
270
271   save_errno = 0;
272
273   if (connect (fd, sa, sa_size) < 0) {
274     save_errno = errno;
275     SigInt = 0;                 /* reset in case we caught SIGINTR while in connect() */
276   }
277
278   if (ConnectTimeout > 0)
279     alarm (0);
280   mutt_allow_interrupt (0);
281
282   return save_errno;
283 }
284
285 /* socket_new_conn: allocate and initialise a new connection. */
286 static CONNECTION *socket_new_conn (void)
287 {
288   CONNECTION *conn;
289
290   conn = p_new(CONNECTION, 1);
291   conn->fd = -1;
292
293   return conn;
294 }
295
296 int raw_socket_close (CONNECTION * conn)
297 {
298   return close (conn->fd);
299 }
300
301 int raw_socket_read (CONNECTION * conn, char *buf, ssize_t len)
302 {
303   int rc;
304
305   if ((rc = read (conn->fd, buf, len)) == -1) {
306     mutt_error (_("Error talking to %s (%s)"), conn->account.host,
307                 strerror (errno));
308     mutt_sleep (2);
309   }
310
311   return rc;
312 }
313
314 int raw_socket_write (CONNECTION * conn, const char *buf, ssize_t count)
315 {
316   int rc;
317
318   if ((rc = write (conn->fd, buf, count)) == -1) {
319     mutt_error (_("Error talking to %s (%s)"), conn->account.host,
320                 strerror (errno));
321     mutt_sleep (2);
322   }
323
324   return rc;
325 }
326
327 int raw_socket_open (CONNECTION * conn)
328 {
329   int rc;
330   int fd;
331
332   char *host_idna = NULL;
333
334 #ifdef HAVE_GETADDRINFO
335 /* --- IPv4/6 --- */
336
337   /* "65536\0" */
338   char port[6];
339   struct addrinfo hints;
340   struct addrinfo *res;
341   struct addrinfo *cur;
342
343   /* we accept v4 or v6 STREAM sockets */
344   p_clear(&hints, 1);
345
346   if (option (OPTUSEIPV6))
347     hints.ai_family = AF_UNSPEC;
348   else
349     hints.ai_family = AF_INET;
350
351   hints.ai_socktype = SOCK_STREAM;
352
353   snprintf (port, sizeof (port), "%d", conn->account.port);
354
355 # ifdef HAVE_LIBIDN
356   if (idna_to_ascii_lz (conn->account.host, &host_idna, 1) != IDNA_SUCCESS) {
357     mutt_error (_("Bad IDN \"%s\"."), conn->account.host);
358     return -1;
359   }
360 # else
361   host_idna = conn->account.host;
362 # endif
363
364   mutt_message (_("Looking up %s..."), conn->account.host);
365
366
367   rc = getaddrinfo (host_idna, port, &hints, &res);
368
369 # ifdef HAVE_LIBIDN
370   p_delete(&host_idna);
371 # endif
372
373   if (rc) {
374     mutt_error (_("Could not find the host \"%s\""), conn->account.host);
375     mutt_sleep (2);
376     return -1;
377   }
378
379   mutt_message (_("Connecting to %s..."), conn->account.host);
380
381   rc = -1;
382   for (cur = res; cur != NULL; cur = cur->ai_next) {
383     fd = socket (cur->ai_family, cur->ai_socktype, cur->ai_protocol);
384     if (fd >= 0) {
385       if ((rc = socket_connect (fd, cur->ai_addr)) == 0) {
386         fcntl (fd, F_SETFD, FD_CLOEXEC);
387         conn->fd = fd;
388         break;
389       }
390       else
391         close (fd);
392     }
393   }
394
395   freeaddrinfo (res);
396
397 #else
398   /* --- IPv4 only --- */
399
400   struct sockaddr_in sin;
401   struct hostent *he;
402   int i;
403
404   p_clear(&sin, 1);
405   sin.sin_port = htons (conn->account.port);
406   sin.sin_family = AF_INET;
407
408 # ifdef HAVE_LIBIDN
409   if (idna_to_ascii_lz (conn->account.host, &host_idna, 1) != IDNA_SUCCESS) {
410     mutt_error (_("Bad IDN \"%s\"."), conn->account.host);
411     return -1;
412   }
413 # else
414   host_idna = conn->account.host;
415 # endif
416
417   mutt_message (_("Looking up %s..."), conn->account.host);
418
419   if ((he = gethostbyname (host_idna)) == NULL) {
420 # ifdef HAVE_LIBIDN
421     p_delete(&host_idna);
422 # endif
423     mutt_error (_("Could not find the host \"%s\""), conn->account.host);
424
425     return -1;
426   }
427
428 # ifdef HAVE_LIBIDN
429   p_delete(&host_idna);
430 # endif
431
432   mutt_message (_("Connecting to %s..."), conn->account.host);
433
434   rc = -1;
435   for (i = 0; he->h_addr_list[i] != NULL; i++) {
436     memcpy (&sin.sin_addr, he->h_addr_list[i], he->h_length);
437     fd = socket (PF_INET, SOCK_STREAM, IPPROTO_IP);
438
439     if (fd >= 0) {
440       if ((rc = socket_connect (fd, (struct sockaddr *) &sin)) == 0) {
441         fcntl (fd, F_SETFD, FD_CLOEXEC);
442         conn->fd = fd;
443         break;
444       }
445       else
446         close (fd);
447     }
448   }
449
450 #endif
451   if (rc) {
452     mutt_error (_("Could not connect to %s (%s)."), conn->account.host,
453                 (rc > 0) ? strerror (rc) : _("unknown error"));
454     mutt_sleep (2);
455     return -1;
456   }
457
458   return 0;
459 }