是将任意长度的消息变成固定长度的短消息,它类似于一个自变量是消息的函数,也就是Hash函数。数字摘要就是采用单项Hash函数将需要加密的明文“摘要”成一串固定长度(128位)的密文这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。
http://blog.csdn.net/jiangwlee/article/details/11817579、
http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue
0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
0x47,0x74,0xE8,0x33,
};
static unsigned char dh512_g[]={0x02,};
DH *dh=DH_new();
dh->p=BN_bin2bn(dh512_p,sizeof(dh512_p),NULL);
dh->g=BN_bin2bn(dh512_g,sizeof(dh512_g),NULL);
SSL_CTX_set_tmp_dh(ctx,dh);
Command-line parameter generation:
$ openssl dhparam -out dh_param_2048.pem 2048
Code for setting up parameters during server initialization:
...
SSL_CTX ctx = SSL_CTX_new();
...
/* Set up ephemeral DH parameters. */
DH *dh_2048 = NULL;
FILE *paramfile;
paramfile = fopen("dh_param_2048.pem", "r");
if (paramfile) {
dh_2048 = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
fclose(paramfile);
} else {
/* Error. */
}
if (dh_2048 == NULL) {
/* Error. */
}
if (SSL_CTX_set_tmp_dh(ctx, dh_2048) != 1) {
/* Error. */
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
...
BIO *bio = BIO_new_file(path_to_a_file_with_dhparams, "r");
DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
SSL_CTX_set_tmp_dh(ctx, dh);
DH_free(dh);
ECDH-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);
int SSL_set_cipher_list(SSL *ssl, const char *str);
http://www.openssl.org/docs/ssl/SSL_CTX_set_tmp_dh_callback.html
https://github.com/ice799/stud/tree/d849bb8684585df7033323ea15bf6ac0de468075
http://blog.csdn.net/dog250/article/details/24552307
http://blog.csdn.net/jiejiaozhufu/article/details/8302211
http://blog.chinaunix.net/uid-14779297-id-1988328.htm
http://rrsongzi-gmail-com.iteye.com/blog/600816
http://blog.csdn.net/kkxgx/article/details/12868181
/**
* Copyright 2011 Bump Technologies, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BUMP TECHNOLOGIES, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BUMP TECHNOLOGIES, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Bump Technologies, Inc.
*
**/
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#include <stddef.h>
/* Tweak these for potential memory/throughput tradeoffs */
#define RING_SLOTS 3
#define RING_DATA_LEN 1024 * 32
typedef struct bufent {
char data[RING_DATA_LEN];
char *ptr;
size_t left;
struct bufent *next;
} bufent;
typedef struct ringbuffer {
bufent slots[RING_SLOTS];
bufent *head; // reads from the head
bufent *tail; // writes to the tail
size_t used;
} ringbuffer;
void ringbuffer_init(ringbuffer *rb);
char * ringbuffer_read_next(ringbuffer *rb, int * length);
void ringbuffer_read_skip(ringbuffer *rb, int length);
void ringbuffer_read_pop(ringbuffer *rb);
char * ringbuffer_write_ptr(ringbuffer *rb);
void ringbuffer_write_append(ringbuffer *rb, int length);
int ringbuffer_size(ringbuffer *rb);
int ringbuffer_capacity(ringbuffer *rb);
int ringbuffer_is_empty(ringbuffer *rb);
int ringbuffer_is_full(ringbuffer *rb);
#endif /* RINGBUFFER_H */
/**
* Copyright 2011 Bump Technologies, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BUMP TECHNOLOGIES, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BUMP TECHNOLOGIES, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Bump Technologies, Inc.
*
**/
#include "ringbuffer.h"
#include <assert.h>
/* Initialize a ringbuffer structure to empty */
void ringbuffer_init(ringbuffer *rb) {
rb->head = &rb->slots[0];
rb->tail = &rb->slots[0];
rb->used = 0;
int x;
for (x=0; x<RING_SLOTS; x++)
rb->slots[x].next = &(rb->slots[(x + 1) % RING_SLOTS]);
}
/** READ FUNCTIONS **/
/* Return a char * that represents the current unconsumed buffer */
char * ringbuffer_read_next(ringbuffer *rb, int * length) {
assert(rb->used);
*length = rb->head->left;
return rb->head->ptr;
}
/* Mark consumption of only part of the read head buffer */
void ringbuffer_read_skip(ringbuffer *rb, int length) {
assert(rb->used);
rb->head->ptr += length;
rb->head->left -= length;
}
/* Pop a consumed (fully read) head from the buffer */
void ringbuffer_read_pop(ringbuffer *rb) {
assert(rb->used);
rb->head = rb->head->next;
rb->used--;
}
/** WRITE FUNCTIONS **/
/* Return the tail ptr (current target of new writes) */
char * ringbuffer_write_ptr(ringbuffer *rb) {
assert(rb->used < RING_SLOTS);
return rb->tail->data;
}
/* Mark the tail appended for `length` bytes, and move the cursor
* to the next slot */
void ringbuffer_write_append(ringbuffer *rb, int length) {
assert(rb->used < RING_SLOTS);
rb->used++;
rb->tail->ptr = rb->tail->data;
rb->tail->left = length;
rb->tail = rb->tail->next;
}
/** RING STATE FUNCTIONS **/
/* Used size of the ringbuffer */
int ringbuffer_size(ringbuffer *rb) {
return rb->used;
}
/* Used size of the ringbuffer */
int ringbuffer_capacity(ringbuffer *rb) {
(void) rb;
return RING_SLOTS;
}
/* Is the ringbuffer completely empty (implies: no data to be written) */
int ringbuffer_is_empty(ringbuffer *rb) {
return rb->used == 0;
}
/* Is the ringbuffer completely full (implies: no more data should be read) */
int ringbuffer_is_full(ringbuffer *rb) {
return rb->used == RING_SLOTS;
}
/**
* Copyright 2011 Bump Technologies, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BUMP TECHNOLOGIES, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BUMP TECHNOLOGIES, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Bump Technologies, Inc.
*
**/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>
#include <pwd.h>
#include <limits.h>
#include <sched.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <ev.h>
#include "ringbuffer.h"
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
#ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0
#endif
/* Globals */
static struct ev_loop *loop;
static struct addrinfo *backaddr;
static pid_t master_pid;
static ev_io listener;
static int listener_socket;
static int child_num;
/* Command line Options */
typedef enum {
ENC_TLS,
ENC_SSL
} ENC_TYPE;
typedef struct stud_options {
ENC_TYPE ETYPE;
int WRITE_IP_OCTET;
int WRITE_PROXY_LINE;
char* CHROOT;
uid_t UID;
gid_t GID;
char *FRONT_IP;
char *FRONT_PORT;
char *BACK_IP;
char *BACK_PORT;
long NCORES;
char *CERT_FILE;
char *CIPHER_SUITE;
} stud_options;
static stud_options OPTIONS = {
ENC_TLS, // ETYPE
0, // WRITE_IP_OCTET
0, // WRITE_PROXY_LINE
NULL, // CHROOT
0, // UID
0, // GID
NULL, // FRONT_IP
"8443", // FRONT_PORT
"127.0.0.1", // BACK_IP
"8000", // BACK_PORT
1, // NCORES
NULL, // CERT_FILE
NULL // CIPHER_SUITE
};
static char tcp4_proxy_line[64] = "";
static char tcp6_proxy_line[128] = "";
/* What agent/state requests the shutdown--for proper half-closed
* handling */
typedef enum _SHUTDOWN_REQUESTOR {
SHUTDOWN_HARD,
SHUTDOWN_DOWN,
SHUTDOWN_UP
} SHUTDOWN_REQUESTOR;
/*
* Proxied State
*
* All state associated with one proxied connection
*/
typedef struct proxystate {
ringbuffer ring_down; /* pushing bytes from client to backend */
ringbuffer ring_up; /* pushing bytes from backend to client */
ev_io ev_r_up; /* Upstream write event */
ev_io ev_w_up; /* Upstream read event */
ev_io ev_r_handshake; /* Downstream write event */
ev_io ev_w_handshake; /* Downstream read event */
ev_io ev_r_down; /* Downstream write event */
ev_io ev_w_down; /* Downstream read event */
int fd_up; /* Upstream (client) socket */
int fd_down; /* Downstream (backend) socket */
int want_shutdown; /* Connection is half-shutdown */
SSL *ssl; /* OpenSSL SSL state */
struct sockaddr_storage remote_ip; /* Remote ip returned from `accept` */
} proxystate;
/* set a file descriptor (socket) to non-blocking mode */
static void setnonblocking(int fd) {
int flag = 1;
assert (ioctl(fd, FIONBIO, &flag) == 0);
}
static void fail(char* s) {
perror(s);
exit(1);
}
#ifndef OPENSSL_NO_DH
static int init_dh(SSL_CTX *ctx, const char *cert) {
DH *dh;
BIO *bio;
if (!cert) {
fprintf(stderr, "No certificate available to load DH parameters\n");
return -1;
}
bio = BIO_new_file(cert, "r");
if (!bio) {
ERR_print_errors_fp(stderr);
return -1;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if (!dh) {
fprintf(stderr, "Could not load DH parameters from %s\n", cert);
return -1;
}
fprintf(stderr, "Using DH parameters from %s\n", cert);
SSL_CTX_set_tmp_dh(ctx, dh);
fprintf(stderr, "DH initialized with %d bit key\n", 8*DH_size(dh));
DH_free(dh);
return 0;
}
#endif /* OPENSSL_NO_DH */
/* Init library and load specified certificate.
* Establishes a SSL_ctx, to act as a template for
* each connection */
static SSL_CTX * init_openssl() {
SSL_library_init();
SSL_load_error_strings();
SSL_CTX *ctx = NULL;
if (OPTIONS.ETYPE == ENC_TLS)
ctx = SSL_CTX_new(TLSv1_server_method());
else if (OPTIONS.ETYPE == ENC_SSL)
ctx = SSL_CTX_new(SSLv23_server_method());
else
assert(OPTIONS.ETYPE == ENC_TLS || OPTIONS.ETYPE == ENC_SSL);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_ALL |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
if (SSL_CTX_use_certificate_file(ctx, OPTIONS.CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(1);
}
if (SSL_CTX_use_RSAPrivateKey_file(ctx, OPTIONS.CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(1);
}
#ifndef OPENSSL_NO_DH
init_dh(ctx, OPTIONS.CERT_FILE);
#endif /* OPENSSL_NO_DH */
if (OPTIONS.CIPHER_SUITE)
if (SSL_CTX_set_cipher_list(ctx, OPTIONS.CIPHER_SUITE) != 1)
ERR_print_errors_fp(stderr);
return ctx;
}
static void prepare_proxy_line(struct sockaddr* ai_addr) {
tcp4_proxy_line[0] = 0;
tcp6_proxy_line[0] = 0;
if (ai_addr->sa_family == AF_INET) {
struct sockaddr_in* addr = (struct sockaddr_in*)ai_addr;
size_t res = snprintf(tcp4_proxy_line,
sizeof(tcp4_proxy_line),
"PROXY TCP4 %%s %s %%hu %hu\r\n",
inet_ntoa(addr->sin_addr),
ntohs(addr->sin_port));
assert(res < sizeof(tcp4_proxy_line));
}
else {
// TODO: AF_INET6
fprintf(stderr, "The --write-proxy mode is not implemented for this address family.\n");
exit(1);
}
}
/* Create the bound socket in the parent process */
static int create_main_socket() {
struct addrinfo *ai, hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
const int gai_err = getaddrinfo(OPTIONS.FRONT_IP, OPTIONS.FRONT_PORT,
&hints, &ai);
if (gai_err != 0) {
fprintf(stderr, "{getaddrinfo}: [%s]\n", gai_strerror(gai_err));
exit(1);
}
int s = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
int t = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(int));
#ifdef SO_REUSEPORT
setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &t, sizeof(int));
#endif
setnonblocking(s);
if (bind(s, ai->ai_addr, ai->ai_addrlen)) {
fail("{bind-socket}");
}
prepare_proxy_line(ai->ai_addr);
freeaddrinfo(ai);
listen(s, 100);
return s;
}
/* Initiate a clear-text nonblocking connect() to the backend IP on behalf
* of a newly connected upstream (encrypted) client*/
static int create_back_socket() {
int s = socket(backaddr->ai_family, SOCK_STREAM, IPPROTO_TCP);
int t = 1;
setnonblocking(s);
t = connect(s, backaddr->ai_addr, backaddr->ai_addrlen);
if (t == 0 || errno == EINPROGRESS || errno == EINTR)
return s;
perror("{backend-connect}");
return -1;
}
/* Only enable a libev ev_io event if the proxied connection still
* has both up and down connected */
static void safe_enable_io(proxystate *ps, ev_io *w) {
if (!ps->want_shutdown)
ev_io_start(loop, w);
}
/* Only enable a libev ev_io event if the proxied connection still
* has both up and down connected */
static void shutdown_proxy(proxystate *ps, SHUTDOWN_REQUESTOR req) {
if (ps->want_shutdown || req == SHUTDOWN_HARD) {
ev_io_stop(loop, &ps->ev_w_up);
ev_io_stop(loop, &ps->ev_r_up);
ev_io_stop(loop, &ps->ev_w_handshake);
ev_io_stop(loop, &ps->ev_r_handshake);
ev_io_stop(loop, &ps->ev_w_down);
ev_io_stop(loop, &ps->ev_r_down);
close(ps->fd_up);
close(ps->fd_down);
SSL_free(ps->ssl);
free(ps);
}
else {
ps->want_shutdown = 1;
if (req == SHUTDOWN_DOWN && ringbuffer_is_empty(&ps->ring_up))
shutdown_proxy(ps, SHUTDOWN_HARD);
else if (req == SHUTDOWN_UP && ringbuffer_is_empty(&ps->ring_down))
shutdown_proxy(ps, SHUTDOWN_HARD);
}
}
/* Handle various socket errors */
static void handle_socket_errno(proxystate *ps) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return;
if (errno == ECONNRESET)
fprintf(stderr, "{backend} Connection reset by peer\n");
else if (errno == ETIMEDOUT)
fprintf(stderr, "{backend} Connection to backend timed out\n");
else if (errno == EPIPE)
fprintf(stderr, "{backend} Broken pipe to backend (EPIPE)\n");
else
perror("{backend} [errno]");
shutdown_proxy(ps, SHUTDOWN_DOWN);
}
/* Read some data from the backend when libev says data is available--
* write it into the upstream buffer and make sure the write event is
* enabled for the upstream socket */
static void back_read(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
proxystate *ps = (proxystate *)w->data;
if (ps->want_shutdown) {
ev_io_stop(loop, &ps->ev_r_down);
return;
}
int fd = w->fd;
char * buf = ringbuffer_write_ptr(&ps->ring_up);
t = recv(fd, buf, RING_DATA_LEN, 0);
if (t > 0) {
ringbuffer_write_append(&ps->ring_up, t);
if (ringbuffer_is_full(&ps->ring_up))
ev_io_stop(loop, &ps->ev_r_down);
safe_enable_io(ps, &ps->ev_w_up);
}
else if (t == 0) {
fprintf(stderr, "{backend} Connection closed\n");
shutdown_proxy(ps, SHUTDOWN_DOWN);
}
else {
assert(t == -1);
handle_socket_errno(ps);
}
}
/* Write some data, previously received on the secure upstream socket,
* out of the downstream buffer and onto the backend socket */
static void back_write(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
proxystate *ps = (proxystate *)w->data;
int fd = w->fd;
int sz;
assert(!ringbuffer_is_empty(&ps->ring_down));
char *next = ringbuffer_read_next(&ps->ring_down, &sz);
t = send(fd, next, sz, MSG_NOSIGNAL);
if (t > 0) {
if (t == sz) {
ringbuffer_read_pop(&ps->ring_down);
safe_enable_io(ps, &ps->ev_r_up);
if (ringbuffer_is_empty(&ps->ring_down)) {
if (ps->want_shutdown) {
shutdown_proxy(ps, SHUTDOWN_HARD);
return; // dealloc'd
}
ev_io_stop(loop, &ps->ev_w_down);
}
}
else {
ringbuffer_read_skip(&ps->ring_down, t);
}
}
else {
assert(t == -1);
handle_socket_errno(ps);
}
}
static void start_handshake(proxystate *ps, int err);
/* Continue/complete the asynchronous connect() before starting data transmission
* between front/backend */
static void handle_connect(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
proxystate *ps = (proxystate *)w->data;
t = connect(ps->fd_down, backaddr->ai_addr, backaddr->ai_addrlen);
if (!t || errno == EISCONN || !errno) {
/* INIT */
ev_io_stop(loop, &ps->ev_w_down);
ev_io_init(&ps->ev_r_down, back_read, ps->fd_down, EV_READ);
ev_io_init(&ps->ev_w_down, back_write, ps->fd_down, EV_WRITE);
start_handshake(ps, SSL_ERROR_WANT_READ); /* for client-first handshake */
ev_io_start(loop, &ps->ev_r_down);
if (OPTIONS.WRITE_PROXY_LINE) {
char *ring_pnt = ringbuffer_write_ptr(&ps->ring_down);
struct sockaddr_in* addr = (struct sockaddr_in*)&ps->remote_ip;
assert(ps->remote_ip.ss_family == AF_INET);
size_t written = snprintf(ring_pnt,
RING_DATA_LEN,
tcp4_proxy_line,
inet_ntoa(addr->sin_addr),
ntohs(addr->sin_port));
ringbuffer_write_append(&ps->ring_down, written);
ev_io_start(loop, &ps->ev_w_down);
}
else if (OPTIONS.WRITE_IP_OCTET) {
char *ring_pnt = ringbuffer_write_ptr(&ps->ring_down);
assert(ps->remote_ip.ss_family == AF_INET ||
ps->remote_ip.ss_family == AF_INET6);
*ring_pnt++ = (unsigned char) ps->remote_ip.ss_family;
if (ps->remote_ip.ss_family == AF_INET6) {
memcpy(ring_pnt, &((struct sockaddr_in6 *) &ps->remote_ip)
->sin6_addr.s6_addr, 16U);
ringbuffer_write_append(&ps->ring_down, 1U + 16U);
} else {
memcpy(ring_pnt, &((struct sockaddr_in *) &ps->remote_ip)
->sin_addr.s_addr, 4U);
ringbuffer_write_append(&ps->ring_down, 1U + 4U);
}
ev_io_start(loop, &ps->ev_w_down);
}
}
else if (errno == EINPROGRESS || errno == EINTR || errno == EALREADY) {
/* do nothing, we'll get phoned home again... */
}
else {
perror("{backend-connect}");
shutdown_proxy(ps, SHUTDOWN_HARD);
}
}
/* Upon receiving a signal from OpenSSL that a handshake is required, re-wire
* the read/write events to hook up to the handshake handlers */
static void start_handshake(proxystate *ps, int err) {
ev_io_stop(loop, &ps->ev_r_up);
ev_io_stop(loop, &ps->ev_w_up);
if (err == SSL_ERROR_WANT_READ)
ev_io_start(loop, &ps->ev_r_handshake);
else if (err == SSL_ERROR_WANT_WRITE)
ev_io_start(loop, &ps->ev_w_handshake);
}
/* After OpenSSL is done with a handshake, re-wire standard read/write handlers
* for data transmission */
static void end_handshake(proxystate *ps) {
ev_io_stop(loop, &ps->ev_r_handshake);
ev_io_stop(loop, &ps->ev_w_handshake);
/* if incoming buffer is not full */
if (!ringbuffer_is_full(&ps->ring_down))
safe_enable_io(ps, &ps->ev_r_up);
/* if outgoing buffer is not empty */
if (!ringbuffer_is_empty(&ps->ring_up))
// not safe.. we want to resume stream even during half-closed
ev_io_start(loop, &ps->ev_w_up);
}
/* The libev I/O handler during the OpenSSL handshake phase. Basically, just
* let OpenSSL do what it likes with the socket and obey its requests for reads
* or writes */
static void client_handshake(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
proxystate *ps = (proxystate *)w->data;
t = SSL_do_handshake(ps->ssl);
if (t == 1) {
end_handshake(ps);
}
else {
int err = SSL_get_error(ps->ssl, t);
if (err == SSL_ERROR_WANT_READ) {
ev_io_stop(loop, &ps->ev_w_handshake);
ev_io_start(loop, &ps->ev_r_handshake);
}
else if (err == SSL_ERROR_WANT_WRITE) {
ev_io_stop(loop, &ps->ev_r_handshake);
ev_io_start(loop, &ps->ev_w_handshake);
}
else if (err == SSL_ERROR_ZERO_RETURN) {
fprintf(stderr, "{client} Connection closed (in handshake)\n");
shutdown_proxy(ps, SHUTDOWN_UP);
}
else {
fprintf(stderr, "{client} Unexpected SSL error (in handshake): %d\n", err);
shutdown_proxy(ps, SHUTDOWN_UP);
}
}
}
/* Handle a socket error condition passed to us from OpenSSL */
static void handle_fatal_ssl_error(proxystate *ps, int err) {
if (err == SSL_ERROR_ZERO_RETURN)
fprintf(stderr, "{client} Connection closed (in data)\n");
else if (err == SSL_ERROR_SYSCALL)
if (errno == 0)
fprintf(stderr, "{client} Connection closed (in data)\n");
else
perror("{client} [errno] ");
else
fprintf(stderr, "{client} Unexpected SSL_read error: %d\n", err);
shutdown_proxy(ps, SHUTDOWN_UP);
}
/* Read some data from the upstream secure socket via OpenSSL,
* and buffer anything we get for writing to the backend */
static void client_read(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
proxystate *ps = (proxystate *)w->data;
if (ps->want_shutdown) {
ev_io_stop(loop, &ps->ev_r_up);
return;
}
char * buf = ringbuffer_write_ptr(&ps->ring_down);
t = SSL_read(ps->ssl, buf, RING_DATA_LEN);
if (t > 0) {
ringbuffer_write_append(&ps->ring_down, t);
if (ringbuffer_is_full(&ps->ring_down))
ev_io_stop(loop, &ps->ev_r_up);
safe_enable_io(ps, &ps->ev_w_down);
}
else {
int err = SSL_get_error(ps->ssl, t);
if (err == SSL_ERROR_WANT_WRITE) {
start_handshake(ps, err);
}
else if (err == SSL_ERROR_WANT_READ) { } /* incomplete SSL data */
else
handle_fatal_ssl_error(ps, err);
}
}
/* Write some previously-buffered backend data upstream on the
* secure socket using OpenSSL */
static void client_write(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
int t;
int sz;
proxystate *ps = (proxystate *)w->data;
assert(!ringbuffer_is_empty(&ps->ring_up));
char * next = ringbuffer_read_next(&ps->ring_up, &sz);
t = SSL_write(ps->ssl, next, sz);
if (t > 0) {
if (t == sz) {
ringbuffer_read_pop(&ps->ring_up);
safe_enable_io(ps, &ps->ev_r_down); // can be re-enabled b/c we've popped
if (ringbuffer_is_empty(&ps->ring_up)) {
if (ps->want_shutdown) {
shutdown_proxy(ps, SHUTDOWN_HARD);
return;
}
ev_io_stop(loop, &ps->ev_w_up);
}
}
else {
ringbuffer_read_skip(&ps->ring_up, t);
}
}
else {
int err = SSL_get_error(ps->ssl, t);
if (err == SSL_ERROR_WANT_READ) {
start_handshake(ps, err);
}
else if (err == SSL_ERROR_WANT_WRITE) {} /* incomplete SSL data */
else
handle_fatal_ssl_error(ps, err);
}
}
/* libev read handler for the bound socket. Socket is accepted,
* the proxystate is allocated and initalized, and we're off the races
* connecting to the backend */
static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
struct sockaddr_storage addr;
socklen_t sl = sizeof(addr);
int client = accept(w->fd, (struct sockaddr *) &addr, &sl);
if (client == -1) {
assert(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN);
return;
}
setnonblocking(client);
int back = create_back_socket();
if (back == -1) {
close(client);
perror("{backend-connect}");
return;
}
SSL_CTX * ctx = (SSL_CTX *)w->data;
SSL *ssl = SSL_new(ctx);
SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_accept_state(ssl);
SSL_set_fd(ssl, client);
proxystate *ps = (proxystate *)malloc(sizeof(proxystate));
ps->fd_up = client;
ps->fd_down = back;
ps->ssl = ssl;
ps->want_shutdown = 0;
ps->remote_ip = addr;
ringbuffer_init(&ps->ring_up);
ringbuffer_init(&ps->ring_down);
/* set up events */
ev_io_init(&ps->ev_r_up, client_read, client, EV_READ);
ev_io_init(&ps->ev_w_up, client_write, client, EV_WRITE);
ev_io_init(&ps->ev_r_handshake, client_handshake, client, EV_READ);
ev_io_init(&ps->ev_w_handshake, client_handshake, client, EV_WRITE);
ev_io_init(&ps->ev_w_down, handle_connect, back, EV_WRITE);
ev_io_start(loop, &ps->ev_w_down);
ps->ev_r_up.data = ps;
ps->ev_w_up.data = ps;
ps->ev_r_down.data = ps;
ps->ev_w_down.data = ps;
ps->ev_r_handshake.data = ps;
ps->ev_w_handshake.data = ps;
}
static void check_ppid(struct ev_loop *loop, ev_timer *w, int revents) {
(void) revents;
pid_t ppid = getppid();
if (ppid != master_pid) {
fprintf(stderr, "{core} Process %d detected parent death, closing listener socket.\n", child_num);
ev_timer_stop(loop, w);
ev_io_stop(loop, &listener);
close(listener_socket);
}
}
/* Set up the child (worker) process including libev event loop, read event
* on the bound socket, etc */
static void handle_connections(SSL_CTX *ctx) {
fprintf(stderr, "{core} Process %d online\n", child_num);
#if defined(CPU_ZERO) && defined(CPU_SET)
cpu_set_t cpus;
CPU_ZERO(&cpus);
CPU_SET(child_num, &cpus);
int res = sched_setaffinity(0, sizeof(cpus), &cpus);
if (!res)
fprintf(stderr, "{core} Successfully attached to CPU #%d\n", child_num);
else
fprintf(stderr, "{core-warning} Unable to attach to CPU #%d; do you have that many cores?\n", child_num);
#endif
loop = ev_default_loop(EVFLAG_AUTO);
ev_timer timer_ppid_check;
ev_timer_init(&timer_ppid_check, check_ppid, 1.0, 1.0);
ev_timer_start(loop, &timer_ppid_check);
ev_io_init(&listener, handle_accept, listener_socket, EV_READ);
listener.data = ctx;
ev_io_start(loop, &listener);
ev_loop(loop, 0);
fprintf(stderr, "{core} Child %d exiting.\n", child_num);
exit(1);
}
/* Print usage w/error message and exit failure */
static void usage_fail(char *prog, char *msg) {
if (msg)
fprintf(stderr, "%s: %s\n", prog, msg);
fprintf(stderr, "usage: %s [OPTION] PEM\n", prog);
fprintf(stderr,
"Encryption Methods:\n"
" --tls (TLSv1, default)\n"
" --ssl (SSLv3)\n"
" -c CIPHER_SUITE (set allowed ciphers)\n"
"\n"
"Socket:\n"
" -b HOST,PORT (backend [connect], default \"127.0.0.1,8000\")\n"
" -f HOST,PORT (frontend [bind], default \"*,8443\")\n"
"\n"
"Performance:\n"
" -n CORES (number of worker processes, default 1)\n"
"\n"
"Security:\n"
" -r PATH (chroot)\n"
" -u USERNAME (set gid/uid after binding the socket)\n"
"\n"
"Special:\n"
" --write-ip (write 1 octet with the IP family followed by\n"
" 4 (IPv4) or 16 (IPv6) octets little-endian\n"
" to backend before the actual data)\n"
" --write-proxy (write HaProxy's PROXY protocol line before actual data:\n"
" \"PROXY TCP4 <source-ip> <dest-ip> <source-port> <dest-port>\\r\\n\"\n"
" Note, that currently only TCP4 implemented. Also note, that dest-ip\n"
" and dest-port are initialized once after the socket is bound. It means\n"
" that you will get 0.0.0.0 as dest-ip instead of actual IP if that what\n"
" the listening socket was bound to)\n"
);
exit(1);
}
static void parse_host_and_port(char *prog, char *name, char *inp, int wildcard_okay, char **ip, char **port) {
char buf[150];
char *sp;
if (strlen(inp) >= sizeof buf) {
snprintf(buf, sizeof buf, "invalid option for %s HOST,PORT\n", name);
usage_fail(prog, buf);
}
sp = strchr(inp, ',');
if (!sp) {
snprintf(buf, sizeof buf, "invalid option for %s HOST,PORT\n", name);
usage_fail(prog, buf);
}
if (!strncmp(inp, "*", sp - inp)) {
if (!wildcard_okay) {
snprintf(buf, sizeof buf, "wildcard host specification invalid for %s\n", name);
usage_fail(prog, buf);
}
*ip = NULL;
}
else {
*sp = 0;
*ip = inp;
}
*port = sp + 1;
}
/* Handle command line arguments modifying behavior */
static void parse_cli(int argc, char **argv) {
char *prog = argv[0];
static int tls = 0, ssl = 0;
int c;
struct passwd* passwd;
static struct option long_options[] =
{
{"tls", 0, &tls, 1},
{"ssl", 0, &ssl, 1},
{"write-ip", 0, &OPTIONS.WRITE_IP_OCTET, 1},
{"write-proxy", 0, &OPTIONS.WRITE_PROXY_LINE, 1},
{0, 0, 0, 0}
};
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "hf:b:n:c:u:r:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
break;
case 'n':
errno = 0;
OPTIONS.NCORES = strtol(optarg, NULL, 10);
if ((errno == ERANGE &&
(OPTIONS.NCORES == LONG_MAX || OPTIONS.NCORES == LONG_MIN)) ||
OPTIONS.NCORES < 1 || OPTIONS.NCORES > 128) {
usage_fail(prog, "invalid option for -n CORES; please provide an integer between 1 and 128\n");
}
break;
case 'b':
parse_host_and_port(prog, "-b", optarg, 0, &(OPTIONS.BACK_IP), &(OPTIONS.BACK_PORT));
break;
case 'f':
parse_host_and_port(prog, "-f", optarg, 1, &(OPTIONS.FRONT_IP), &(OPTIONS.FRONT_PORT));
break;
case 'c':
OPTIONS.CIPHER_SUITE = optarg;
break;
case 'u':
passwd = getpwnam(optarg);
if (!passwd) {
if (errno)
fail("getpwnam failed");
else
fprintf(stderr, "user not found: %s\n", optarg);
exit(1);
}
OPTIONS.UID = passwd->pw_uid;
OPTIONS.GID = passwd->pw_gid;
break;
case 'r':
if (optarg && optarg[0] == '/')
OPTIONS.CHROOT = optarg;
else {
fprintf(stderr, "chroot must be absolute path: \"%s\"\n", optarg);
exit(1);
}
break;
default:
usage_fail(prog, NULL);
}
}
/* Post-processing */
if (tls && ssl)
usage_fail(prog, "Cannot specify both --tls and --ssl");
if (ssl)
OPTIONS.ETYPE = ENC_SSL; // implied.. else, TLS
if (OPTIONS.WRITE_IP_OCTET && OPTIONS.WRITE_PROXY_LINE)
usage_fail(prog, "Cannot specify both --write-ip and --write-proxy; pick one!");
argc -= optind;
argv += optind;
if (argc != 1)
usage_fail(prog, "exactly one argument is required: path to PEM file with cert/key");
OPTIONS.CERT_FILE = argv[0];
}
void change_root() {
if (chroot(OPTIONS.CHROOT) == -1)
fail("chroot");
if (chdir("/"))
fail("chdir");
}
void drop_privileges() {
if (setgid(OPTIONS.GID))
fail("setgid failed");
if (setuid(OPTIONS.UID))
fail("setuid failed");
}
/* Process command line args, create the bound socket,
* spawn child (worker) processes, and wait for them all to die
* (which they shouldn't!) */
int main(int argc, char **argv) {
parse_cli(argc, argv);
listener_socket = create_main_socket();
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
const int gai_err = getaddrinfo(OPTIONS.BACK_IP, OPTIONS.BACK_PORT,
&hints, &backaddr);
if (gai_err != 0) {
fprintf(stderr, "{getaddrinfo}: [%s]", gai_strerror(gai_err));
exit(1);
}
/* load certificate, pass to handle_connections */
SSL_CTX * ctx = init_openssl();
master_pid = getpid();
if (OPTIONS.CHROOT && OPTIONS.CHROOT[0])
change_root();
if (OPTIONS.UID || OPTIONS.GID)
drop_privileges();
for (child_num=0; child_num < OPTIONS.NCORES; child_num++) {
int pid = fork();
if (pid == -1) {
fprintf(stderr, "{core} fork() failed! Goodbye cruel world!\n");
exit(1);
}
else if (pid == 0) // child
goto handle;
}
int child_status;
for (child_num=0; child_num < OPTIONS.NCORES; child_num++) {
wait(&child_status);
fprintf(stderr, "{core} A child died! This should not happen! Goodbye cruel world!\n");
exit(2);
}
handle:
handle_connections(ctx);
return 0;
}