purr-c/comm.c

128 lines
4.2 KiB
C

#define _POSIX_C_SOURCE 200112L /* fdopen */
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include "purr.h"
#include "translation.h"
int send_and_receive(struct connection_information *ci)
{
int rv = 0;
int socket_write = dup(ci->socket);
if (socket_write < 0) {
perror("dup()");
return rv;
}
struct transmission_information ti =
{.ioc = ci->ioc,
.no_strip = ci->no_strip, .debug = ci->debug,
.socket = ci->socket,
// stream is created here and applies close-on-exec to fd.
// slightly racy, unfortunately.
.socket_write_stream = fdopen(socket_write, "we"),
.ssl = ci->ssl,
.type = ci->type,
.header_callback = ci->header_callback};
if (ti.socket_write_stream == NULL) {
perror("fdopen()");
close(socket_write);
return rv;
}
// remove buffering from the socket stream
setbuf(ti.socket_write_stream, NULL);
ti.file = ci->input;
if (ti.ssl) {
br_sslio_write_all(ci->ioc, ci->request, ci->request_size);
if (ci->alpn) {
if (ci->request_size == 0) {
// force some transmission so handshake is performed and ALPN is negotiated.
// this is necessary here because an empty request won't generate a handshake.
br_sslio_flush(ci->ioc);
}
const char *alpn_name = br_ssl_engine_get_selected_protocol(&ci->sc->eng);
if (ci->debug) {
// this isn't critical information
if (alpn_name == NULL) {
fputs(_("ALPN mismatch\n"), stderr);
} else {
fprintf(stderr, _("ALPN: %s\n"), alpn_name);
}
}
}
} else {
fwrite(ci->request, 1, ci->request_size, ti.socket_write_stream);
}
if (ci->send) {
size_t sent = mmap_to_ssl(ti);
if (sent == 0) {
fputs(_("warning: empty input file...\n"), stderr);
}
if (ci->debug) {
fprintf(stderr, _("wrote %lu bytes!\n"), sent);
}
}
if (ti.ssl) {
br_sslio_write_all(ci->ioc, ci->footer, ci->footer_size);
br_sslio_flush(ci->ioc);
} else {
fwrite(ci->footer, 1, ci->footer_size, ti.socket_write_stream);
}
ti.file = ci->output;
if (ssl_to_mmap(ti) == 0) {
fputs(_("warning: empty response...\n"), stderr);
}
if (ti.ssl) {
// situation around close_notify is fuzzy:
// relevant RFCs say it is required, but lots of server impls
// don't respond to it, due to HTTP/1.0 onward requiring self-termination
// (in the form of Content-Length, for our case).
// therefore, only complain about it in debug mode.
// source: https://security.stackexchange.com/questions/82028/ssl-tls-is-a-server-always-required-to-respond-to-a-close-notify
if (br_sslio_close(ci->ioc) != 0) {
if (ci->debug) {
fputs(_("TLS: couldn't close SSL connection!\n"), stderr);
}
}
// check whether everything was closed properly:
// leaving the connection hanging is ok, per the above comment.
// errors checked relate to certificate errors and the kind.
// the caller shouldn't use the buffers passed to this function if it
// returns an error.
if (br_ssl_engine_current_state(&ci->sc->eng) == BR_SSL_CLOSED) {
const int err = br_ssl_engine_last_error(&ci->sc->eng);
if (err == BR_ERR_OK) {
if (ci->debug) fputs(_("TLS: all good!\n"), stderr);
rv = EXIT_SUCCESS;
} else if (err == BR_ERR_IO) {
if (ci->debug) fputs(_("TLS: non-critical I/O error\n"), stderr);
rv = EXIT_SUCCESS;
} else {
fprintf(stderr, _("TLS: BearSSL error: %d\n"), err);
rv = EXIT_FAILURE;
}
} else {
// this case shouldn't happen, since br_sslio_close is called above.
if (ci->debug) fputs(_("socket closed without terminating ssl!\n"), stderr);
rv = EXIT_SUCCESS;
}
}
fclose(ti.socket_write_stream);
return rv;
}