some fixes wrt imap and pop authentication.
[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 int mutt_socket_readln2(buffer_t *buf, CONNECTION *conn)
154 {
155     char ch;
156
157     while (mutt_socket_readchar(conn, &ch) == 1) {
158         if (ch == '\n') {
159             if (buf->len && buf->data[buf->len - 1] == '\r') {
160                 buf->data[--buf->len] = '\0';
161             }
162             return 0;
163         }
164         buffer_addch(buf, ch);
165     }
166
167     return -1;
168 }
169
170 CONNECTION *mutt_socket_head (void)
171 {
172   return Connections;
173 }
174
175 /* mutt_socket_free: remove connection from connection list and free it */
176 void mutt_socket_free (CONNECTION * conn)
177 {
178   CONNECTION *iter;
179   CONNECTION *tmp;
180
181   iter = Connections;
182
183   /* head is special case, doesn't need prev updated */
184   if (iter == conn) {
185     Connections = iter->next;
186     p_delete(&iter);
187     return;
188   }
189
190   while (iter->next) {
191     if (iter->next == conn) {
192       tmp = iter->next;
193       iter->next = tmp->next;
194       p_delete(&tmp);
195       return;
196     }
197     iter = iter->next;
198   }
199 }
200
201 /* mutt_conn_find: find a connection off the list of connections whose
202  *   account matches account. If start is not null, only search for
203  *   connections after the given connection (allows higher level socket code
204  *   to make more fine-grained searches than account info - eg in IMAP we may
205  *   wish to find a connection which is not in IMAP_SELECTED state) */
206 CONNECTION *mutt_conn_find (const CONNECTION * start, const ACCOUNT * account)
207 {
208   CONNECTION *conn;
209   ciss_url_t url;
210   char hook[LONG_STRING];
211
212   /* account isn't actually modified, since url isn't either */
213   mutt_account_tourl ((ACCOUNT *) account, &url);
214   url.path = NULL;
215   url_ciss_tostring (&url, hook, sizeof (hook), 0);
216   mutt_account_hook (hook);
217
218   conn = start ? start->next : Connections;
219   while (conn) {
220     if (mutt_account_match (account, &(conn->account)))
221       return conn;
222     conn = conn->next;
223   }
224
225   conn = socket_new_conn ();
226   memcpy (&conn->account, account, sizeof (ACCOUNT));
227
228   conn->next = Connections;
229   Connections = conn;
230
231   if (Tunnel && *Tunnel)
232     mutt_tunnel_socket_setup (conn);
233   else if (account->has_ssl) {
234     if (mutt_ssl_socket_setup (conn) < 0) {
235       mutt_socket_free (conn);
236       return NULL;
237     }
238   } else {
239     conn->conn_read = raw_socket_read;
240     conn->conn_write = raw_socket_write;
241     conn->conn_open = raw_socket_open;
242     conn->conn_close = raw_socket_close;
243   }
244
245   return conn;
246 }
247
248 static int socket_preconnect (void)
249 {
250   int rc;
251   int save_errno;
252
253   if (m_strlen(Preconnect)) {
254     rc = mutt_system (Preconnect);
255     if (rc) {
256       save_errno = errno;
257       mutt_perror (_("Preconnect command failed."));
258       mutt_sleep (1);
259
260       return save_errno;
261     }
262   }
263
264   return 0;
265 }
266
267 /* socket_connect: set up to connect to a socket fd. */
268 static int socket_connect (int fd, struct sockaddr *sa)
269 {
270   int sa_size;
271   int save_errno;
272
273   if (sa->sa_family == AF_INET)
274     sa_size = sizeof (struct sockaddr_in);
275   else if (sa->sa_family == AF_INET6)
276     sa_size = sizeof (struct sockaddr_in6);
277   else {
278     return -1;
279   }
280
281   if (ConnectTimeout > 0)
282     alarm (ConnectTimeout);
283
284   mutt_allow_interrupt (1);
285
286   save_errno = 0;
287
288   if (connect (fd, sa, sa_size) < 0) {
289     save_errno = errno;
290     SigInt = 0;                 /* reset in case we caught SIGINTR while in connect() */
291   }
292
293   if (ConnectTimeout > 0)
294     alarm (0);
295   mutt_allow_interrupt (0);
296
297   return save_errno;
298 }
299
300 /* socket_new_conn: allocate and initialise a new connection. */
301 static CONNECTION *socket_new_conn (void)
302 {
303   CONNECTION *conn;
304
305   conn = p_new(CONNECTION, 1);
306   conn->fd = -1;
307
308   return conn;
309 }
310
311 int raw_socket_close (CONNECTION * conn)
312 {
313   return close (conn->fd);
314 }
315
316 int raw_socket_read (CONNECTION * conn, char *buf, ssize_t len)
317 {
318   int rc;
319
320   if ((rc = read (conn->fd, buf, len)) == -1) {
321     mutt_error (_("Error talking to %s (%s)"), conn->account.host,
322                 strerror (errno));
323     mutt_sleep (2);
324   }
325
326   return rc;
327 }
328
329 int raw_socket_write (CONNECTION * conn, const char *buf, ssize_t count)
330 {
331   int rc;
332
333   if ((rc = write (conn->fd, buf, count)) == -1) {
334     mutt_error (_("Error talking to %s (%s)"), conn->account.host,
335                 strerror (errno));
336     mutt_sleep (2);
337   }
338
339   return rc;
340 }
341
342 int raw_socket_open (CONNECTION * conn)
343 {
344   int rc;
345   int fd;
346
347   char *host_idna = NULL;
348
349   /* "65536\0" */
350   char port[6];
351   struct addrinfo hints;
352   struct addrinfo *res;
353   struct addrinfo *cur;
354
355   /* we accept v4 or v6 STREAM sockets */
356   p_clear(&hints, 1);
357
358   if (option (OPTUSEIPV6))
359     hints.ai_family = AF_UNSPEC;
360   else
361     hints.ai_family = AF_INET;
362
363   hints.ai_socktype = SOCK_STREAM;
364
365   snprintf (port, sizeof (port), "%d", conn->account.port);
366
367 # ifdef HAVE_LIBIDN
368   if (idna_to_ascii_lz (conn->account.host, &host_idna, 1) != IDNA_SUCCESS) {
369     mutt_error (_("Bad IDN \"%s\"."), conn->account.host);
370     return -1;
371   }
372 # else
373   host_idna = conn->account.host;
374 # endif
375
376   mutt_message (_("Looking up %s..."), conn->account.host);
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   if (rc) {
409     mutt_error (_("Could not connect to %s (%s)."), conn->account.host,
410                 (rc > 0) ? strerror (rc) : _("unknown error"));
411     mutt_sleep (2);
412     return -1;
413   }
414
415   return 0;
416 }