re/src/http/server.c
Richard Aas 1be9aa335c This patch includes multiple TLS fixes:
- General TLS improvements
- New DTLS API
- OpenSSL multi-threading support
2014-06-12 10:51:53 +00:00

519 lines
8.6 KiB
C

/**
* @file http/server.c HTTP Server
*
* Copyright (C) 2011 Creytiv.com
*/
#include <re_types.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_fmt.h>
#include <re_tmr.h>
#include <re_srtp.h>
#include <re_tcp.h>
#include <re_tls.h>
#include <re_msg.h>
#include <re_http.h>
enum {
TIMEOUT_IDLE = 600000,
TIMEOUT_INIT = 10000,
BUFSIZE_MAX = 524288,
};
struct http_sock {
struct list connl;
struct tcp_sock *ts;
struct tls *tls;
http_req_h *reqh;
void *arg;
};
struct http_conn {
struct le le;
struct tmr tmr;
struct sa peer;
struct http_sock *sock;
struct tcp_conn *tc;
struct tls_conn *sc;
struct mbuf *mb;
};
static void conn_close(struct http_conn *conn);
static void sock_destructor(void *arg)
{
struct http_sock *sock = arg;
struct le *le;
for (le=sock->connl.head; le;) {
struct http_conn *conn = le->data;
le = le->next;
conn_close(conn);
mem_deref(conn);
}
mem_deref(sock->tls);
mem_deref(sock->ts);
}
static void conn_destructor(void *arg)
{
struct http_conn *conn = arg;
list_unlink(&conn->le);
tmr_cancel(&conn->tmr);
mem_deref(conn->sc);
mem_deref(conn->tc);
mem_deref(conn->mb);
}
static void conn_close(struct http_conn *conn)
{
list_unlink(&conn->le);
tmr_cancel(&conn->tmr);
conn->sc = mem_deref(conn->sc);
conn->tc = mem_deref(conn->tc);
conn->sock = NULL;
}
static void timeout_handler(void *arg)
{
struct http_conn *conn = arg;
conn_close(conn);
mem_deref(conn);
}
static void recv_handler(struct mbuf *mb, void *arg)
{
struct http_conn *conn = arg;
int err = 0;
if (conn->mb) {
const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
err = EOVERFLOW;
goto out;
}
conn->mb->pos = conn->mb->end;
err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
if (err)
goto out;
conn->mb->pos = pos;
}
else {
conn->mb = mem_ref(mb);
}
while (conn->mb) {
size_t end, pos = conn->mb->pos;
struct http_msg *msg;
err = http_msg_decode(&msg, conn->mb, true);
if (err) {
if (err == ENODATA) {
conn->mb->pos = pos;
err = 0;
break;
}
goto out;
}
if (mbuf_get_left(conn->mb) < msg->clen) {
conn->mb->pos = pos;
mem_deref(msg);
break;
}
mb = conn->mb;
end = mb->end;
mb->end = mb->pos + msg->clen;
if (end > mb->end) {
struct mbuf *mbn = mbuf_alloc(end - mb->end);
if (!mbn) {
mem_deref(msg);
err = ENOMEM;
goto out;
}
(void)mbuf_write_mem(mbn, mb->buf + mb->end,
end - mb->end);
mbn->pos = 0;
mem_deref(conn->mb);
conn->mb = mbn;
}
else {
conn->mb = mem_deref(conn->mb);
}
if (conn->sock)
conn->sock->reqh(conn, msg, conn->sock->arg);
mem_deref(msg);
if (!conn->tc) {
err = ENOTCONN;
goto out;
}
tmr_start(&conn->tmr, TIMEOUT_IDLE, timeout_handler, conn);
}
out:
if (err) {
conn_close(conn);
mem_deref(conn);
}
}
static void close_handler(int err, void *arg)
{
struct http_conn *conn = arg;
(void)err;
conn_close(conn);
mem_deref(conn);
}
static void connect_handler(const struct sa *peer, void *arg)
{
struct http_sock *sock = arg;
struct http_conn *conn;
int err;
conn = mem_zalloc(sizeof(*conn), conn_destructor);
if (!conn) {
err = ENOMEM;
goto out;
}
list_append(&sock->connl, &conn->le, conn);
conn->peer = *peer;
conn->sock = sock;
err = tcp_accept(&conn->tc, sock->ts, NULL, recv_handler,
close_handler, conn);
if (err)
goto out;
#ifdef USE_TLS
if (sock->tls) {
err = tls_start_tcp(&conn->sc, sock->tls, conn->tc, 0);
if (err)
goto out;
}
#endif
tmr_start(&conn->tmr, TIMEOUT_INIT, timeout_handler, conn);
out:
if (err) {
mem_deref(conn);
tcp_reject(sock->ts);
}
}
/**
* Create an HTTP socket
*
* @param sockp Pointer to returned HTTP Socket
* @param laddr Network address to listen on
* @param reqh Request handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int http_listen(struct http_sock **sockp, const struct sa *laddr,
http_req_h *reqh, void *arg)
{
struct http_sock *sock;
int err;
if (!sockp || !laddr || !reqh)
return EINVAL;
sock = mem_zalloc(sizeof(*sock), sock_destructor);
if (!sock)
return ENOMEM;
err = tcp_listen(&sock->ts, laddr, connect_handler, sock);
if (err)
goto out;
sock->reqh = reqh;
sock->arg = arg;
out:
if (err)
mem_deref(sock);
else
*sockp = sock;
return err;
}
/**
* Create an HTTP secure socket
*
* @param sockp Pointer to returned HTTP Socket
* @param laddr Network address to listen on
* @param cert File path of TLS certificate
* @param reqh Request handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int https_listen(struct http_sock **sockp, const struct sa *laddr,
const char *cert, http_req_h *reqh, void *arg)
{
struct http_sock *sock;
int err;
if (!sockp || !laddr || !cert || !reqh)
return EINVAL;
err = http_listen(&sock, laddr, reqh, arg);
if (err)
return err;
#ifdef USE_TLS
err = tls_alloc(&sock->tls, TLS_METHOD_SSLV23, cert, NULL);
#else
err = EPROTONOSUPPORT;
#endif
if (err)
goto out;
out:
if (err)
mem_deref(sock);
else
*sockp = sock;
return err;
}
/**
* Get the TCP socket of an HTTP socket
*
* @param sock HTTP socket
*
* @return TCP socket
*/
struct tcp_sock *http_sock_tcp(struct http_sock *sock)
{
return sock ? sock->ts : NULL;
}
/**
* Get the peer address of an HTTP connection
*
* @param conn HTTP connection
*
* @return Peer address
*/
const struct sa *http_conn_peer(const struct http_conn *conn)
{
return conn ? &conn->peer : NULL;
}
/**
* Get the TCP connection of an HTTP connection
*
* @param conn HTTP connection
*
* @return TCP connection
*/
struct tcp_conn *http_conn_tcp(struct http_conn *conn)
{
return conn ? conn->tc : NULL;
}
/**
* Get the TLS connection of an HTTP connection
*
* @param conn HTTP connection
*
* @return TLS connection
*/
struct tls_conn *http_conn_tls(struct http_conn *conn)
{
return conn ? conn->sc : NULL;
}
/**
* Close the HTTP connection
*
* @param conn HTTP connection
*/
void http_conn_close(struct http_conn *conn)
{
if (!conn)
return;
conn->sc = mem_deref(conn->sc);
conn->tc = mem_deref(conn->tc);
}
static int http_vreply(struct http_conn *conn, uint16_t scode,
const char *reason, const char *fmt, va_list ap)
{
struct mbuf *mb;
int err;
if (!conn || !scode || !reason)
return EINVAL;
if (!conn->tc)
return ENOTCONN;
mb = mbuf_alloc(8192);
if (!mb)
return ENOMEM;
err = mbuf_printf(mb, "HTTP/1.1 %u %s\r\n", scode, reason);
if (fmt)
err |= mbuf_vprintf(mb, fmt, ap);
else
err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n");
if (err)
goto out;
mb->pos = 0;
err = tcp_send(conn->tc, mb);
if (err)
goto out;
out:
mem_deref(mb);
return err;
}
/**
* Send an HTTP response
*
* @param conn HTTP connection
* @param scode Response status code
* @param reason Response reason phrase
* @param fmt Formatted HTTP message
*
* @return 0 if success, otherwise errorcode
*/
int http_reply(struct http_conn *conn, uint16_t scode, const char *reason,
const char *fmt, ...)
{
va_list ap;
int err;
va_start(ap, fmt);
err = http_vreply(conn, scode, reason, fmt, ap);
va_end(ap);
return err;
}
/**
* Send an HTTP response with content formatting
*
* @param conn HTTP connection
* @param scode Response status code
* @param reason Response reason phrase
* @param ctype Content type
* @param fmt Formatted HTTP content
*
* @return 0 if success, otherwise errorcode
*/
int http_creply(struct http_conn *conn, uint16_t scode, const char *reason,
const char *ctype, const char *fmt, ...)
{
struct mbuf *mb;
va_list ap;
int err;
if (!ctype || !fmt)
return EINVAL;
mb = mbuf_alloc(8192);
if (!mb)
return ENOMEM;
va_start(ap, fmt);
err = mbuf_vprintf(mb, fmt, ap);
va_end(ap);
if (err)
goto out;
err = http_reply(conn, scode, reason,
"Content-Type: %s\r\n"
"Content-Length: %zu\r\n"
"\r\n"
"%b",
ctype,
mb->end,
mb->buf, mb->end);
if (err)
goto out;
out:
mem_deref(mb);
return err;
}
/**
* Send an HTTP error response
*
* @param conn HTTP connection
* @param scode Response status code
* @param reason Response reason phrase
*
* @return 0 if success, otherwise errorcode
*/
int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason)
{
return http_creply(conn, scode, reason, "text/html",
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>%u %s</title></head>\n"
"<body><h2>%u %s</h2></body>\n"
"</html>\n",
scode, reason,
scode, reason);
}