/* ssl (plain) -> app */ static void stream_plain_source_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_source); switch (event) { case LI_STREAM_NEW_DATA: do_gnutls_read(f); if (f->write_wants_read) do_gnutls_write(f); li_stream_notify(stream); break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* app */ break; case LI_STREAM_CONNECTED_SOURCE: /* crypt_drain */ break; case LI_STREAM_DISCONNECTED_DEST: /* app */ if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: /* crypt_drain */ if (!stream->out->is_closed) { f_abort_gnutls(f); /* didn't get everything */ } break; case LI_STREAM_DESTROY: f_release(f); break; } }
/* app -> ssl (plain) */ static void stream_plain_drain_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_drain); switch (event) { case LI_STREAM_NEW_DATA: if (!stream->out->is_closed && NULL != stream->source) { li_chunkqueue_steal_all(stream->out, stream->source->out); stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed; } do_gnutls_write(f); if (stream->out->is_closed) { li_stream_disconnect(stream); stream->out->is_closed = FALSE; } break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* crypt_source */ break; case LI_STREAM_CONNECTED_SOURCE: /* app */ break; case LI_STREAM_DISCONNECTED_DEST: if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: if (!stream->out->is_closed) { f_abort_gnutls(f); /* didn't get everything */ } break; case LI_STREAM_DESTROY: f_release(f); break; } }
/* ssl crypted out -> io */ static void stream_crypt_source_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_source); switch (event) { case LI_STREAM_NEW_DATA: /* data comes through SSL */ break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* io out */ break; case LI_STREAM_CONNECTED_SOURCE: /* plain_drain */ break; case LI_STREAM_DISCONNECTED_DEST: /* io out disconnect */ if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: /* plain_drain */ if (!stream->out->is_closed) { /* f_close_ssl before we were ready */ f_abort_gnutls(f); } break; case LI_STREAM_DESTROY: f_release(f); break; } }
/* io -> ssl crypted in */ static void stream_crypt_drain_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_drain); switch (event) { case LI_STREAM_NEW_DATA: if (!stream->out->is_closed && NULL != stream->source) { li_chunkqueue_steal_all(stream->out, stream->source->out); stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed; li_stream_notify(stream); /* tell plain_source to do SSL_read */ } if (stream->out->is_closed) { li_stream_disconnect(stream); } break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* plain_source */ break; case LI_STREAM_CONNECTED_SOURCE: /* io in */ break; case LI_STREAM_DISCONNECTED_DEST: /* plain_source */ if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: /* io in disconnect */ if (!stream->out->is_closed) { f_abort_gnutls(f); /* conn aborted */ } break; case LI_STREAM_DESTROY: f_release(f); break; } }
static void f_close_with_alert(liGnuTLSFilter *f, int r) { if (f->closing || f->aborted) return; if (GNUTLS_E_SUCCESS != gnutls_alert_send_appropriate(f->session, r)) { f_abort_gnutls(f); return; } /* push alert to io */ li_stream_notify(&f->crypt_source); f->plain_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; f_close_gnutls(f); }
static void do_handle_error(liGnuTLSFilter *f, const char *gnutlsfunc, int r, gboolean writing) { switch (r) { case GNUTLS_E_AGAIN: if (writing) f->write_wants_read = TRUE; break; case GNUTLS_E_REHANDSHAKE: #ifdef HAVE_SAVE_RENEGOTIATION if (f->initial_handshaked_finished && !gnutls_safe_renegotiation_status(f->session)) { _ERROR(f->srv, f->wrk, f->log_context, "%s: client initiated unsafe renegotitation, closing connection", gnutlsfunc); f_close_with_alert(f, r); } else { _DEBUG(f->srv, f->wrk, f->log_context, "%s: client initiated renegotitation", gnutlsfunc); } #else if (f->initial_handshaked_finished) { _ERROR(f->srv, f->wrk, f->log_context, "%s: client initiated renegotitation, closing connection", gnutlsfunc); f_close_with_alert(f, r); } #endif break; case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: f_close_with_alert(f, r); break; case GNUTLS_E_UNKNOWN_CIPHER_SUITE: case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: _DEBUG(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); f_close_with_alert(f, r); break; default: if (gnutls_error_is_fatal(r)) { _ERROR(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); if (f->initial_handshaked_finished) { f_close_with_alert(f, r); } else { f_abort_gnutls(f); } } else { _ERROR(f->srv, f->wrk, f->log_context, "%s non fatal (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); } } }
static void do_gnutls_write(liGnuTLSFilter *f) { const ssize_t blocksize = 16*1024; /* 16k */ char *block_data; off_t block_len; ssize_t r; off_t write_max; #ifdef USE_CORK gboolean corked = FALSE; #endif liChunkQueue *cq = f->plain_drain.out; f_acquire(f); f->write_wants_read = FALSE; /* use space in (encrypted) outgoing buffer as amounts of bytes we try to write from (plain) output * don't care if we write a little bit more than the limit allowed */ write_max = li_chunkqueue_limit_available(f->crypt_source.out); LI_FORCE_ASSERT(write_max >= 0); /* we set a limit! */ if (0 == write_max) goto out; /* if we start writing, try to write at least blocksize bytes */ if (write_max < blocksize) write_max = blocksize; if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, TRUE)) goto out; if (NULL == f->session) { f_abort_gnutls(f); goto out; } #ifdef USE_CORK if (0 != cq->length && cq->queue.length > 1) { corked = TRUE; gnutls_record_cork(f->session); } #endif do { GError *err = NULL; liChunkIter ci; if (0 == cq->length) break; ci = li_chunkqueue_iter(cq); switch (li_chunkiter_read(ci, 0, blocksize, &block_data, &block_len, &err)) { case LI_HANDLER_GO_ON: break; case LI_HANDLER_ERROR: if (NULL != err) { _ERROR(f->srv, f->wrk, f->log_context, "Couldn't read data from chunkqueue: %s", err->message); g_error_free(err); } /* fall through */ default: f_abort_gnutls(f); goto out; } r = gnutls_record_send(f->session, block_data, block_len); if (r <= 0) { do_handle_error(f, "gnutls_record_send", r, TRUE); goto out; } li_chunkqueue_skip(cq, r); write_max -= r; } while (r == block_len && write_max > 0); if (cq->is_closed && 0 == cq->length) { r = gnutls_bye(f->session, GNUTLS_SHUT_RDWR); switch (r) { case GNUTLS_E_SUCCESS: case GNUTLS_E_AGAIN: case GNUTLS_E_INTERRUPTED: f->plain_source.out->is_closed = TRUE; f->crypt_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; li_stream_disconnect(&f->crypt_source); /* plain in -> crypt out */ f_close_gnutls(f); break; default: do_handle_error(f, "gnutls_bye", r, TRUE); f_abort_gnutls(f); break; } } else if (0 < cq->length && 0 != li_chunkqueue_limit_available(f->crypt_source.out)) { li_stream_again_later(&f->plain_drain); } out: #ifdef USE_CORK if (NULL != f->session && corked) { corked = TRUE; gnutls_record_uncork(f->session, 0); } #endif f_release(f); }
static void do_gnutls_read(liGnuTLSFilter *f) { const ssize_t blocksize = 16*1024; /* 16k */ off_t max_read = 4 * blocksize; /* 64k */ ssize_t r; off_t len = 0; liChunkQueue *cq = f->plain_source.out; f_acquire(f); if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, FALSE)) goto out; if (NULL == f->session) { f_abort_gnutls(f); goto out; } do { liBuffer *buf; gboolean cq_buf_append; buf = li_chunkqueue_get_last_buffer(cq, 1024); cq_buf_append = (buf != NULL); if (buf != NULL) { /* use last buffer as raw_in_buffer; they should be the same anyway */ if (G_UNLIKELY(buf != f->raw_in_buffer)) { li_buffer_acquire(buf); li_buffer_release(f->raw_in_buffer); f->raw_in_buffer = buf; } } else { buf = f->raw_in_buffer; if (buf != NULL && buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); f->raw_in_buffer = buf = NULL; } if (buf == NULL) { f->raw_in_buffer = buf = li_buffer_new(blocksize); } } LI_FORCE_ASSERT(f->raw_in_buffer == buf); r = gnutls_record_recv(f->session, buf->addr + buf->used, buf->alloc_size - buf->used); if (r < 0) { do_handle_error(f, "gnutls_record_recv", r, FALSE); goto out; } else if (r == 0) { /* clean shutdown? */ f->plain_source.out->is_closed = TRUE; f->plain_drain.out->is_closed = TRUE; f->crypt_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; li_stream_disconnect(&f->crypt_drain); /* io -> crypt in */ li_stream_disconnect_dest(&f->crypt_source); /* crypt out -> io */ li_stream_disconnect(&f->crypt_source); /* plain in -> crypt out */ f_close_gnutls(f); goto out; } if (cq_buf_append) { li_chunkqueue_update_last_buffer_size(cq, r); } else { gsize offset; li_buffer_acquire(buf); offset = buf->used; buf->used += r; li_chunkqueue_append_buffer2(cq, buf, offset, r); } if (buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); f->raw_in_buffer = buf = NULL; } len += r; } while (len < max_read); out: f_release(f); }
static void do_handle_error(liGnuTLSFilter *f, const char *gnutlsfunc, int r, gboolean writing) { switch (r) { case GNUTLS_E_AGAIN: if (writing) f->write_wants_read = TRUE; return; case GNUTLS_E_REHANDSHAKE: #ifdef HAVE_SAVE_RENEGOTIATION if (f->initial_handshaked_finished && !gnutls_safe_renegotiation_status(f->session)) { _ERROR(f->srv, f->wrk, f->log_context, "%s: client initiated unsafe renegotitation, closing connection", gnutlsfunc); f_close_with_alert(f, r); } else { _DEBUG(f->srv, f->wrk, f->log_context, "%s: client initiated renegotitation", gnutlsfunc); } #else if (f->initial_handshaked_finished) { _ERROR(f->srv, f->wrk, f->log_context, "%s: client initiated renegotitation, closing connection", gnutlsfunc); f_close_with_alert(f, r); } #endif return; case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: f_close_with_alert(f, r); return; case GNUTLS_E_UNKNOWN_CIPHER_SUITE: case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: _DEBUG(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); f_close_with_alert(f, r); return; case GNUTLS_E_FATAL_ALERT_RECEIVED: case GNUTLS_E_WARNING_ALERT_RECEIVED: { gnutls_alert_description_t alert_desc = gnutls_alert_get(f->session); const char* alert_desc_name = gnutls_alert_get_name(alert_desc); _INFO(f->srv, f->wrk, f->log_context, "%s (%s): %s %s (%u)", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r), (NULL != alert_desc_name) ? alert_desc_name : "unknown alert", (unsigned int) alert_desc); } /* error not handled yet: break instead of return */ break; default: if (gnutls_error_is_fatal(r)) { _ERROR(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); } else { _WARNING(f->srv, f->wrk, f->log_context, "%s non fatal (%s): %s", gnutlsfunc, gnutls_strerror_name(r), gnutls_strerror(r)); } /* error not handled yet: break instead of return */ break; } /* generic error handling */ if (gnutls_error_is_fatal(r)) { if (f->initial_handshaked_finished) { f_close_with_alert(f, r); } else { f_abort_gnutls(f); } } }