Evtloop fixes.
[apps/madmutt.git] / lib-sys / evtloop.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  */
17 /*
18  *  Copyright © 2007 Pierre Habouzit
19  */
20
21 #include <netdb.h>
22 #include <pthread.h>
23 #include <sys/epoll.h>
24 #include <sys/socket.h>
25 #ifndef EPOLLRDHUP
26 #  include <linux/poll.h>
27 #  ifdef POLLRDHUP
28 #    define EPOLLRDHUP POLLRDHUP
29 #  else
30 #    define EPOLLRDHUP 0
31 #  endif
32 #endif
33 #include "evtloop.h"
34 #include "mutt.h"
35 #include "mutt_ssl.li"
36 #ifdef HAVE_LIBIDN
37 #include <idna.h>
38 #endif
39
40 DO_ARRAY_TYPE(job_t, job);
41
42 static int epollfd = -1;
43 static job_array jobs;
44 static pthread_mutex_t el_mx;
45 static pthread_cond_t el_cond;
46 static pthread_t el_thread;
47
48 static int el_job_setemode(job_t *w, el_mode emode)
49 {
50     static int const evtmode_to_epoll[] = {
51         [EL_NEW]     = EPOLLRDHUP,
52         [EL_READING] = EPOLLIN,
53         [EL_WRITING] = EPOLLOUT,
54         [EL_RDWR]    = EPOLLIN | EPOLLOUT,
55         [EL_IDLE]    = EPOLLRDHUP,
56     };
57
58     assert (w->mode == emode || emode == EL_WRITING || emode == EL_READING);
59
60     if (emode != w->emode) {
61         struct epoll_event event = {
62             .data.ptr = w,
63             .events   = evtmode_to_epoll[emode],
64         };
65         int action = w->emode == EL_NEW ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
66         if (epoll_ctl(epollfd, action, w->fd, &event) < 0) {
67             return el_job_release(w, true);
68         }
69     }
70     w->emode = emode;
71     return 0;
72 }
73
74 int el_job_setmode(job_t *w, el_mode mode)
75 {
76     if (w->mode == w->emode) {
77         w->mode = mode;
78         return el_job_setemode(w, mode);
79     } else {
80         w->mode = mode;
81         return 0;
82     }
83 }
84
85 void job_wipe(job_t *w)
86 {
87     if (w->xcred)
88         gnutls_certificate_free_credentials(w->xcred);
89     if (w->session)
90         gnutls_deinit(w->session);
91 }
92
93 static void job_arrau_dtor(job_t **j)
94 {
95     if (*j)
96         IGNORE(el_job_release(*j, EL_KILLED));
97 }
98
99 DO_ARRAY_FUNCS(job_t, job, job_arrau_dtor);
100
101 static void job_array_remove(job_array *arr, job_t *j)
102 {
103     for (int i = 0; i < arr->len; i++) {
104         if (arr->arr[i] == j) {
105             job_array_take(arr, i);
106             break;
107         }
108     }
109 }
110
111 job_t *el_job_start(const machine_t *m, void *cfg)
112 {
113     job_t *w = job_new();
114     w->m = m;
115     job_array_append(&jobs, w);
116     return m->setup(w, cfg) < 0 ? NULL : w;
117 }
118
119 int el_job_release(job_t *w, el_status reason)
120 {
121     if (w->cond) {
122         pthread_cond_signal(&el_cond);
123         w->cond = false;
124     }
125     w->state = EL_LLP_FINI;
126     if (w->m && w->m->finalize) {
127         w->m->finalize(w, reason);
128     }
129     if (w->fd >= 0) {
130         if (w->session)
131             gnutls_bye(w->session, GNUTLS_SHUT_RDWR);
132         close(w->fd);
133     }
134     job_array_remove(&jobs, w);
135     job_delete(&w);
136     return -1;
137 }
138
139 static int el_job_tlsing(job_t *w, int starttls)
140 {
141     int err = gnutls_handshake(w->session);
142     if (err < 0 && !gnutls_error_is_fatal(err)) {
143         int wr = gnutls_record_get_direction(w->session);
144         return el_job_setemode(w, wr ? EL_WRITING : EL_READING);
145     }
146     if (err < 0)
147         return el_job_release(w, EL_RDHUP);
148
149 #if 0
150     if (!tls_check_certificate (conn))
151         return -1;
152 #endif
153
154     /* set Security Strength Factor (SSF) for SASL */
155     /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
156     w->ssf   = gnutls_cipher_get_key_size(gnutls_cipher_get(w->session)) * 8;
157     w->state = EL_LLP_READY;
158     if (starttls)
159         return el_job_setemode(w, w->mode);
160     return w->m->on_event(w, EL_EVT_RUNNING);
161 }
162
163 static int el_job_starttlsing(job_t *w)
164 {
165     return el_job_tlsing(w, true);
166 }
167
168 static int el_job_connecting_ssl(job_t *w)
169 {
170     return el_job_tlsing(w, false);
171 }
172
173 static int el_job_connecting(job_t *w)
174 {
175     int err = 0;
176     socklen_t len = sizeof(err);
177
178     if (getsockopt(w->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &len) || err)
179         return el_job_release(w, EL_ERROR);
180
181     if (w->session) {
182         w->llp = &el_job_connecting_ssl;
183         return w->llp(w);
184     }
185     w->state = EL_LLP_READY;
186     return w->m->on_event(w, EL_EVT_RUNNING);
187 }
188
189 static int tls_negotiate(job_t *w)
190 {
191     static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
192
193     if (gnutls_certificate_allocate_credentials(&w->xcred) < 0)
194         return -1;
195
196     /* ignore errors, maybe file doesn't exist yet */
197     gnutls_certificate_set_x509_trust_file(w->xcred, mod_ssl.cert_file,
198                                            GNUTLS_X509_FMT_PEM);
199
200     if (mod_ssl.ca_certificates_file) {
201         gnutls_certificate_set_x509_trust_file(w->xcred,
202             mod_ssl.ca_certificates_file, GNUTLS_X509_FMT_PEM);
203     }
204     gnutls_init(&w->session, GNUTLS_CLIENT);
205
206     /* set socket */
207     gnutls_transport_set_ptr(w->session, (gnutls_transport_ptr)(intptr_t)w->fd);
208
209     /* disable TLS/SSL protocols as needed */
210     if (!mod_ssl.use_sslv3) {
211         protocol_priority[1] = 0;
212     }
213
214     /* We use default priorities (see gnutls documentation),
215        except for protocol version */
216     gnutls_set_default_priority(w->session);
217     gnutls_protocol_set_priority(w->session, protocol_priority);
218     gnutls_credentials_set(w->session, GNUTLS_CRD_CERTIFICATE, w->xcred);
219     return 0;
220 }
221
222 int el_job_connect(job_t *w, struct sockaddr *addr, socklen_t len,
223                    int type, int proto, int ssl)
224 {
225     int res, sock = socket(addr->sa_family, type, proto);
226
227     if (sock < 0)
228         goto error;
229
230     res = fcntl(sock, F_GETFL);
231     if (res < 0)
232         goto error;
233     if (fcntl(sock, F_SETFL, res | O_NONBLOCK) < 0)
234         goto error;
235     if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0)
236         goto error;
237     if (connect(sock, addr, len) < 0)
238         goto error;
239
240     w->fd  = sock;
241     if (ssl && tls_negotiate(w) < 0)
242         goto error;
243
244     w->llp = &el_job_connecting;
245     return el_job_setmode(w, EL_WRITING);
246
247   error:
248     close(sock);
249     return el_job_release(w, EL_ERROR);
250 }
251
252 int el_job_connect2(job_t *w, const ACCOUNT *act)
253 {
254     int rc;
255     char *host = NULL;
256     struct addrinfo *res;
257     struct addrinfo hints = {
258         .ai_family = AF_UNSPEC,
259         .ai_socktype = SOCK_STREAM,
260     };
261
262 # ifdef HAVE_LIBIDN
263     if (idna_to_ascii_lz(act->host, &host, 1) != IDNA_SUCCESS) {
264         mutt_error(_("Bad IDN \"%s\"."), act->host);
265         return -1;
266     }
267 # else
268     host = act->host;
269 # endif
270     mutt_message(_("Looking up %s..."), act->host);
271     rc = getaddrinfo(host, NULL, &hints, &res);
272 # ifdef HAVE_LIBIDN
273     p_delete(&host);
274 # endif
275
276     if (rc) {
277         mutt_error(_("Could not find the host \"%s\""), act->host);
278         mutt_sleep(2);
279         return -1;
280     }
281     mutt_message(_("Connecting to %s..."), act->host);
282     rc = el_job_connect(w, res->ai_addr, res->ai_addrlen, res->ai_socktype,
283                         res->ai_protocol, act->has_ssl);
284     freeaddrinfo (res);
285     if (rc) {
286         mutt_error(_("Could not connect to %s (%m)."), act->host);
287         mutt_sleep(2);
288         return -1;
289     }
290     return 0;
291 }
292
293 int el_job_starttls(job_t *w)
294 {
295     if (tls_negotiate(w) < 0)
296         return el_job_release(w, EL_RDHUP);
297     w->state = EL_LLP_INIT;
298     w->llp   = &el_job_starttlsing;
299     return w->llp(w);
300 }
301
302 ssize_t el_job_read(job_t *w, buffer_t *buf)
303 {
304     ssize_t nr;
305
306     buffer_ensure(buf, BUFSIZ);
307
308     if (w->session) {
309         nr = gnutls_record_recv(w->session, buf->data + buf->len, BUFSIZ);
310         if (nr < 0 && !gnutls_error_is_fatal(nr)) {
311             int wr = gnutls_record_get_direction(w->session);
312             return el_job_setemode(w, wr ? EL_WRITING : EL_READING);
313         }
314         EL_JOB_CHECK(el_job_setemode(w, w->mode));
315     } else {
316         nr = read(w->fd, buf->data + buf->len, BUFSIZ);
317         if (nr < 0 && (errno == EINTR || errno == EAGAIN))
318             return 0;
319     }
320     if (nr <= 0)
321         return el_job_release(w, EL_RDHUP);
322     buffer_extend(buf, nr);
323     return nr;
324 }
325
326 ssize_t el_job_write(job_t *w, buffer_t *buf)
327 {
328     ssize_t nr;
329
330     if (buf->len == 0)
331         return 0;
332
333     if (w->session) {
334         nr = gnutls_record_send(w->session, buf->data, buf->len);
335         if (nr < 0 && !gnutls_error_is_fatal(nr)) {
336             int wr = gnutls_record_get_direction(w->session);
337             return el_job_setemode(w, wr ? EL_WRITING : EL_READING);
338         }
339         EL_JOB_CHECK(el_job_setemode(w, w->mode));
340     } else {
341         nr = write(w->fd, buf->data, buf->len);
342         if (nr < 0 && (errno == EINTR || errno == EAGAIN))
343             return 0;
344     }
345     if (nr <= 0)
346         return el_job_release(w, EL_RDHUP);
347     buffer_splice(buf, 0, nr, NULL, 0);
348     return nr;
349 }
350
351 void el_lock(void)
352 {
353     pthread_mutex_lock(&el_mx);
354 }
355
356 void el_unlock(void)
357 {
358     pthread_mutex_unlock(&el_mx);
359 }
360
361 int el_dispatch(int timeout)
362 {
363     struct epoll_event events[FD_SETSIZE];
364     int count = epoll_wait(epollfd, events, countof(events), timeout);
365
366     if (count < 0) {
367         if (errno == EAGAIN || errno == EINTR)
368             return 0;
369         mutt_error("epoll_wait");
370         mutt_exit(EXIT_FAILURE);
371     }
372
373     el_lock();
374     while (--count >= 0) {
375         job_t *w  = events[count].data.ptr;
376         int event = events[count].events;
377         int evt   = 0;
378
379         if (w->cond) {
380             pthread_cond_signal(&el_cond);
381             w->cond = false;
382         }
383         gettimeofday(&w->mru, NULL);
384         switch (w->state) {
385           case EL_LLP_INIT:
386             w->llp(w);
387             break;
388
389           case EL_LLP_READY:
390             if (event & EPOLLRDHUP) {
391                 IGNORE(el_job_release(w, EL_RDHUP));
392             } else if (w->mode != w->emode) {
393                 IGNORE(w->m->on_event(w, EL_EVT_INOUT ^ w->emode));
394             } else {
395                 if (event & EPOLLIN)
396                     evt |= EL_EVT_IN;
397                 if (event & EPOLLOUT)
398                     evt |= EL_EVT_OUT;
399                 IGNORE(w->m->on_event(w, evt));
400             }
401             break;
402
403           default:
404             IGNORE(el_job_release(w, EL_ERROR));
405             break;
406         }
407     }
408     el_unlock();
409
410     return 0;
411 }
412
413 void el_wait(volatile job_t *w)
414 {
415     w->cond = true;
416     pthread_cond_wait(&el_cond, &el_mx);
417 }
418
419 static void *el_loop(void *data)
420 {
421     time_t sec = time(NULL);
422
423     for (;;) {
424         struct timeval now;
425
426         el_dispatch(100);
427         pthread_testcancel();
428
429         gettimeofday(&now, NULL);
430         if (sec >= now.tv_sec)
431             continue;
432         sec = now.tv_sec;
433         now.tv_sec -= 10;
434
435         el_lock();
436         for (int i = jobs.len - 1; i >= 0; --i) {
437             job_t *w = jobs.arr[i];
438             if (timercmp(&now, &w->mru, >)) {
439                 if (w->cond) {
440                     pthread_cond_signal(&el_cond);
441                     w->cond = false;
442                 }
443                 IGNORE(w->m->on_event(w, EL_EVT_WAKEUP));
444             }
445         }
446         el_unlock();
447     }
448 }
449
450 void el_initialize(void)
451 {
452     pthread_mutexattr_t attr;
453
454     pthread_mutexattr_init(&attr);
455     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
456     pthread_mutex_init(&el_mx, &attr);
457     pthread_mutexattr_destroy(&attr);
458
459     gnutls_global_init();
460     epollfd = epoll_create(1024);
461     if (epollfd < 0) {
462         mutt_error("epoll_create");
463         mutt_exit(EXIT_FAILURE);
464     }
465     job_array_init(&jobs);
466     pthread_create(&el_thread, NULL, &el_loop, NULL);
467 }
468
469 void el_shutdown(void)
470 {
471     pthread_cancel(el_thread);
472     pthread_join(el_thread, NULL);
473     job_array_wipe(&jobs);
474     close(epollfd);
475     gnutls_global_deinit();
476     pthread_mutex_destroy(&el_mx);
477 }