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