X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=lib-sys%2Fevtloop.c;h=4b2f0d84e65ea9b56e767647f58c839797cd0128;hp=1b5d72309cef952a833b2043f31670f8b729a1be;hb=f8d1899d44b259a0920a52bd7290f15e39fd0d47;hpb=66572f4a9c4574c9f2a2c3ef59526ac66b807b0b diff --git a/lib-sys/evtloop.c b/lib-sys/evtloop.c index 1b5d723..4b2f0d8 100644 --- a/lib-sys/evtloop.c +++ b/lib-sys/evtloop.c @@ -29,9 +29,55 @@ #endif #include "evtloop.h" #include "mutt.h" +#include "mutt_ssl.li" static int epollfd = -1; +static int el_job_setemode(job_t *w, el_mode emode) +{ + static int const evtmode_to_epoll[] = { + [EL_NEW] = EPOLLRDHUP, + [EL_READING] = EPOLLIN, + [EL_WRITING] = EPOLLOUT, + [EL_RDWR] = EPOLLIN | EPOLLOUT, + [EL_IDLE] = EPOLLRDHUP, + }; + + assert (w->mode == emode || emode == EL_WRITING || emode == EL_READING); + + if (emode != w->emode) { + struct epoll_event event = { + .data.ptr = w, + .events = evtmode_to_epoll[emode], + }; + int action = w->emode == EL_NEW ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; + if (epoll_ctl(epollfd, action, w->fd, &event) < 0) { + return el_job_release(w, true); + } + } + w->emode = emode; + return 0; +} + +int el_job_setmode(job_t *w, el_mode mode) +{ + if (w->mode == w->emode) { + w->mode = mode; + return el_job_setemode(w, mode); + } else { + w->mode = mode; + return 0; + } +} + +void job_wipe(job_t *w) +{ + if (w->xcred) + gnutls_certificate_free_credentials(w->xcred); + if (w->session) + gnutls_deinit(w->session); +} + int el_job_release(job_t *w, el_status reason) { w->state = EL_LLP_FINI; @@ -39,12 +85,183 @@ int el_job_release(job_t *w, el_status reason) w->m->finalize(w, reason); } if (w->fd >= 0) { + if (w->session) + gnutls_bye(w->session, GNUTLS_SHUT_RDWR); close(w->fd); } - p_delete(&w); + job_delete(&w); return -1; } +static int el_job_tlsing(job_t *w, int starttls) +{ + int err = gnutls_handshake(w->session); + if (err < 0 && !gnutls_error_is_fatal(err)) { + int wr = gnutls_record_get_direction(w->session); + return el_job_setemode(w, wr ? EL_WRITING : EL_READING); + } + if (err < 0) + return el_job_release(w, EL_RDHUP); + +#if 0 + if (!tls_check_certificate (conn)) + return -1; +#endif + + /* set Security Strength Factor (SSF) for SASL */ + /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ + w->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(w->session)) * 8; + w->state = EL_LLP_READY; + if (starttls) + return el_job_setemode(w, w->mode); + return w->m->on_event(w, EL_EVT_RUNNING); +} + +static int el_job_starttlsing(job_t *w) +{ + return el_job_tlsing(w, true); +} + +static int el_job_connecting_ssl(job_t *w) +{ + return el_job_tlsing(w, false); +} + +static int el_job_connecting(job_t *w) +{ + int err = 0; + socklen_t len = sizeof(err); + + if (getsockopt(w->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &len) || err) + return el_job_release(w, EL_ERROR); + + if (w->session) { + w->llp = &el_job_connecting_ssl; + return w->llp(w); + } + w->state = EL_LLP_READY; + return w->m->on_event(w, EL_EVT_RUNNING); +} + +static int tls_negotiate(job_t *w) +{ + static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + + if (gnutls_certificate_allocate_credentials(&w->xcred) < 0) + return -1; + + /* ignore errors, maybe file doesn't exist yet */ + gnutls_certificate_set_x509_trust_file(w->xcred, mod_ssl.cert_file, + GNUTLS_X509_FMT_PEM); + + if (mod_ssl.ca_certificates_file) { + gnutls_certificate_set_x509_trust_file(w->xcred, + mod_ssl.ca_certificates_file, GNUTLS_X509_FMT_PEM); + } + gnutls_init(&w->session, GNUTLS_CLIENT); + + /* set socket */ + gnutls_transport_set_ptr(w->session, (gnutls_transport_ptr)(intptr_t)w->fd); + + /* disable TLS/SSL protocols as needed */ + if (!mod_ssl.use_sslv3) { + protocol_priority[1] = 0; + } + + /* We use default priorities (see gnutls documentation), + except for protocol version */ + gnutls_set_default_priority(w->session); + gnutls_protocol_set_priority(w->session, protocol_priority); + gnutls_credentials_set(w->session, GNUTLS_CRD_CERTIFICATE, w->xcred); + return 0; +} + +int el_job_connect(job_t *w, struct sockaddr *addr, socklen_t len, + int type, int proto, int ssl) +{ + int res, sock = socket(addr->sa_family, type, proto); + + if (sock < 0) + goto error; + + res = fcntl(sock, F_GETFL); + if (res < 0) + goto error; + if (fcntl(sock, F_SETFL, res | O_NONBLOCK) < 0) + goto error; + if (connect(sock, addr, len) < 0) + goto error; + + w->fd = sock; + if (ssl && tls_negotiate(w) < 0) + goto error; + + w->llp = &el_job_connecting; + return el_job_setmode(w, EL_WRITING); + + error: + close(sock); + return el_job_release(w, EL_ERROR); +} + +int el_job_starttls(job_t *w) +{ + if (tls_negotiate(w) < 0) + return el_job_release(w, EL_RDHUP); + w->state = EL_LLP_INIT; + w->llp = &el_job_starttlsing; + return w->llp(w); +} + +ssize_t el_job_read(job_t *w, buffer_t *buf) +{ + ssize_t nr; + + buffer_ensure(buf, BUFSIZ); + + if (w->session) { + nr = gnutls_record_recv(w->session, buf->data + buf->len, BUFSIZ); + if (nr < 0 && !gnutls_error_is_fatal(nr)) { + int wr = gnutls_record_get_direction(w->session); + return el_job_setemode(w, wr ? EL_WRITING : EL_READING); + } + EL_JOB_CHECK(el_job_setemode(w, w->mode)); + } else { + nr = read(w->fd, buf->data + buf->len, BUFSIZ); + if (nr < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + } + if (nr <= 0) + return el_job_release(w, EL_RDHUP); + buffer_extend(buf, nr); + return nr; +} + +ssize_t el_job_write(job_t *w, buffer_t *buf) +{ + ssize_t nr; + + if (buf->len == 0) + return 0; + + if (w->session) { + nr = gnutls_record_send(w->session, buf->data, buf->len); + if (nr < 0 && !gnutls_error_is_fatal(nr)) { + int wr = gnutls_record_get_direction(w->session); + return el_job_setemode(w, wr ? EL_WRITING : EL_READING); + } + EL_JOB_CHECK(el_job_setemode(w, w->mode)); + } else { + nr = write(w->fd, buf->data, buf->len); + if (nr < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + } + if (nr <= 0) + return el_job_release(w, EL_RDHUP); + buffer_splice(buf, 0, nr, NULL, 0); + return nr; +} + int el_dispatch(int timeout) { struct epoll_event events[FD_SETSIZE]; @@ -71,13 +288,13 @@ int el_dispatch(int timeout) if (event & EPOLLRDHUP) { IGNORE(el_job_release(w, EL_RDHUP)); } else if (w->mode != w->emode) { - w->m->on_event(w, EL_EVT_INOUT ^ w->emode); + IGNORE(w->m->on_event(w, EL_EVT_INOUT ^ w->emode)); } else { if (event & EPOLLIN) evt |= EL_EVT_IN; if (event & EPOLLOUT) evt |= EL_EVT_OUT; - w->m->on_event(w, evt); + IGNORE(w->m->on_event(w, evt)); } break; @@ -89,3 +306,19 @@ int el_dispatch(int timeout) return 0; } + +void el_initialize(void) +{ + gnutls_global_init(); + epollfd = epoll_create(1024); + if (epollfd < 0) { + mutt_error("epoll_create"); + mutt_exit(EXIT_FAILURE); + } +} + +void el_shutdown(void) +{ + close(epollfd); + gnutls_global_deinit(); +}