Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Better PROXY support and SNI #113

Merged
merged 3 commits into from

3 participants

@zenazn

Two patches we're using at Stripe.

zenazn added some commits
@zenazn zenazn Code cleanup
The code had mixed tabs/spaces and trailing whitespace. I standardized
on 4-space indentation for stud.c and deleted all trailing whitespace. I
also cleaned up whitespace in shctx.c (but did not standardize spacing
there)
f99e976
@zenazn zenazn Proxy-PROXY support
A mode that enables transparently passing the PROXY header generated by
HAProxy et. al. through to the backend connection. This can be useful
if you are using a TCP load balancer (such as HAProxy in tcp mode) to
distribute encrypted streams to stud before they are unwrapped.
d78fd49
@zenazn zenazn Add SNI support
If multiple certificates are specified, try to perform Server Name
Indication to serve the most appropriate one. We fall back to the last
certificate presented if none of the previous ones match, making it a
useful place to put a star cert.

A few caveats:
- Certificate names are compared as case-insensitive strings, without
  any special logic for dealing with wildcards. The current workaround
  is to always place wildcard certificates last, where they act as the
  default catch-all.
- Certificates are examined in order. The first certificate that matches
  any given request will be used.
- The name -> certificate mapping is stored in a singly linked list.
  This performs very well for use with a handful of certificates, none
  of which have very many Subject Alternative Names, however sites which
  must serve a large number of certificates or names might find a linear
  list scan on every new connection too slow.
b09da7e
@jamwt jamwt merged commit 0b88039 into from
@jamwt
Owner

Thanks, great stuff.

@fcicq

please see #122, bug introduced by this commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 20, 2012
  1. @zenazn

    Code cleanup

    zenazn authored
    The code had mixed tabs/spaces and trailing whitespace. I standardized
    on 4-space indentation for stud.c and deleted all trailing whitespace. I
    also cleaned up whitespace in shctx.c (but did not standardize spacing
    there)
  2. @zenazn

    Proxy-PROXY support

    zenazn authored
    A mode that enables transparently passing the PROXY header generated by
    HAProxy et. al. through to the backend connection. This can be useful
    if you are using a TCP load balancer (such as HAProxy in tcp mode) to
    distribute encrypted streams to stud before they are unwrapped.
  3. @zenazn

    Add SNI support

    zenazn authored
    If multiple certificates are specified, try to perform Server Name
    Indication to serve the most appropriate one. We fall back to the last
    certificate presented if none of the previous ones match, making it a
    useful place to put a star cert.
    
    A few caveats:
    - Certificate names are compared as case-insensitive strings, without
      any special logic for dealing with wildcards. The current workaround
      is to always place wildcard certificates last, where they act as the
      default catch-all.
    - Certificates are examined in order. The first certificate that matches
      any given request will be used.
    - The name -> certificate mapping is stored in a singly linked list.
      This performs very well for use with a handful of certificates, none
      of which have very many Subject Alternative Names, however sites which
      must serve a large number of certificates or names might find a linear
      list scan on every new connection too slow.
This page is out of date. Refresh to see the latest.
Showing with 455 additions and 212 deletions.
  1. +13 −4 README.md
  2. +110 −71 configuration.c
  3. +7 −1 configuration.h
  4. +14 −15 shctx.c
  5. +311 −121 stud.c
View
17 README.md
@@ -19,9 +19,11 @@ maxmium connection behavior, availability of service, etc.
`stud` will optionally write the client IP address as the first few octets
(depending on IPv4 or IPv6) to the backend--or provide that information
-using HAProxy's PROXY protocol. In this way, backends who care about the
-client IP can still access it even though `stud` itself appears to be the
-connected client.
+using HAProxy's PROXY protocol. When used with the PROXY protocol, `stud` can
+also transparently pass an existing PROXY header to the cleartext stream. This
+is especially useful if a TCP proxy is used in front of `stud`. Using either of
+these techniques, backends who care about the client IP can still access it even
+though `stud` itself appears to be the connected client.
Thanks to a contribution from Emeric at Exceliance (the folks behind HAProxy),
a special build of `stud` can be made that utilitizes shared memory to
@@ -81,7 +83,11 @@ Usage
-----
The only required argument is a path to a PEM file that contains the certificate
-(or a chain of certificates) and private key.
+(or a chain of certificates) and private key. If multiple certificates are
+given, `stud` will attempt to perform SNI (Server Name Indication) on new
+connections, by comparing the indicated name with the names on each of the
+certificates, in order. The first certificate that matches will be used. If none
+of the certificates matches, the last certificate will be used as the default.
Detail about the entire set of options can be found by invoking `stud -h`:
@@ -129,6 +135,9 @@ Detail about the entire set of options can be found by invoking `stud -h`:
--write-proxy Write HaProxy's PROXY (IPv4 or IPv6) protocol line
before actual data
(Default: off)
+ --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line
+ before actual data
+ (Default: off)
-t --test Test configuration and exit
-V --version Print program version and exit
View
181 configuration.c
@@ -46,6 +46,7 @@
#define CFG_WRITE_IP "write-ip"
#define CFG_WRITE_PROXY "write-proxy"
#define CFG_PEM_FILE "pem-file"
+#define CFG_PROXY_PROXY "proxy-proxy"
#ifdef USE_SHARED_CACHE
#define CFG_SHARED_CACHE "shared-cache"
@@ -58,7 +59,7 @@
#define FMT_STR "%s = %s\n"
#define FMT_QSTR "%s = \"%s\"\n"
#define FMT_ISTR "%s = %d\n"
-
+
#define CONFIG_MAX_LINES 10000
#define CONFIG_BUF_SIZE 1024
#define CFG_PARAM_CFGFILE 10000
@@ -107,13 +108,14 @@ stud_config * config_new (void) {
config_error_set("Unable to allocate memory for configuration structure: %s", strerror(errno));
return NULL;
}
-
+
// set default values
r->ETYPE = ENC_TLS;
r->PMODE = SSL_SERVER;
r->WRITE_IP_OCTET = 0;
r->WRITE_PROXY_LINE = 0;
+ r->PROXY_PROXY_LINE = 0;
r->CHROOT = NULL;
r->UID = 0;
r->GID = 0;
@@ -122,7 +124,7 @@ stud_config * config_new (void) {
r->BACK_IP = strdup("127.0.0.1");
r->BACK_PORT = strdup("8000");
r->NCORES = 1;
- r->CERT_FILE = NULL;
+ r->CERT_FILES = NULL;
r->CIPHER_SUITE = NULL;
r->ENGINE = NULL;
r->BACKLOG = 100;
@@ -131,10 +133,10 @@ stud_config * config_new (void) {
r->SHARED_CACHE = 0;
r->SHCUPD_IP = NULL;
r->SHCUPD_PORT = NULL;
-
+
for (int i = 0 ; i < MAX_SHCUPD_PEERS; i++)
memset(&r->SHCUPD_PEERS[i], 0, sizeof(shcupd_peer_opt));
-
+
r->SHCUPD_MCASTIF = NULL;
r->SHCUPD_MCASTTTL = NULL;
#endif
@@ -159,7 +161,14 @@ void config_destroy (stud_config *cfg) {
if (cfg->FRONT_PORT != NULL) free(cfg->FRONT_PORT);
if (cfg->BACK_IP != NULL) free(cfg->BACK_IP);
if (cfg->BACK_PORT != NULL) free(cfg->BACK_PORT);
- if (cfg->CERT_FILE != NULL) free(cfg->CERT_FILE);
+ if (cfg->CERT_FILES != NULL) {
+ struct cert_files *curr = cfg->CERT_FILES, *next;
+ while (cfg->CERT_FILES != NULL) {
+ next = curr->NEXT;
+ free(curr);
+ curr = next;
+ }
+ }
if (cfg->CIPHER_SUITE != NULL) free(cfg->CIPHER_SUITE);
if (cfg->ENGINE != NULL) free(cfg->ENGINE);
@@ -171,7 +180,7 @@ void config_destroy (stud_config *cfg) {
if (cfg->SHCUPD_PEERS[i].ip != NULL)
free(cfg->SHCUPD_PEERS[i].ip);
if (cfg->SHCUPD_PEERS[i].port != NULL)
- free(cfg->SHCUPD_PEERS[i].port);
+ free(cfg->SHCUPD_PEERS[i].port);
}
if (cfg->SHCUPD_MCASTIF != NULL) free(cfg->SHCUPD_MCASTIF);
@@ -244,28 +253,28 @@ char * config_get_value (char *str) {
char * str_rtrim(char *str) {
char *ptr;
int len;
-
+
len = strlen(str);
ptr = str + len - 1;
while (ptr >= str && (isspace((int)*ptr ) || (char) *ptr == '"' || (char) *ptr == '\'')) --ptr;
-
+
ptr[1] = '\0';
-
+
return str;
}
-
+
char * str_ltrim(char *str) {
char *ptr;
int len;
-
+
for (ptr = str; (*ptr && (isspace((int)*ptr) || (char) *ptr == '"' || (char) *ptr == '\'')); ++ptr);
-
+
len = strlen(ptr);
memmove(str, ptr, len + 1);
-
+
return str;
}
-
+
char * str_trim(char *str) {
char *ptr;
ptr = str_rtrim(str);
@@ -300,7 +309,7 @@ int config_param_val_bool (char *val, int *res) {
strcasecmp(val, "1") == 0) {
*res = 1;
}
-
+
return 1;
}
@@ -318,10 +327,10 @@ int config_param_host_port_wildcard (char *str, char **addr, char **port, int wi
// address/port buffers
char port_buf[PORT_LEN];
char addr_buf[ADDR_LEN];
-
+
memset(port_buf, '\0', sizeof(port_buf));
memset(addr_buf, '\0', sizeof(addr_buf));
-
+
// NEW FORMAT: [address]:port
if (*str == '[') {
char *ptr = str + 1;
@@ -330,10 +339,10 @@ int config_param_host_port_wildcard (char *str, char **addr, char **port, int wi
config_error_set("Invalid address '%s'.", str);
return 0;
}
-
+
// address
memcpy(addr_buf, ptr, (x - ptr));
-
+
// port
x += 2;
memcpy(port_buf, x, sizeof(port_buf) - 1);
@@ -351,16 +360,16 @@ int config_param_host_port_wildcard (char *str, char **addr, char **port, int wi
// port
memcpy(port_buf, (++x), sizeof(port_buf));
}
-
+
// printf("PARSED ADDR '%s', PORT '%s'\n", addr_buf, port_buf);
-
+
// check port
int p = atoi(port_buf);
if (p < 1 || p > 65536) {
config_error_set("Invalid port number '%s'", port_buf);
return 0;
}
-
+
// write
if (strcmp(addr_buf, "*") == 0) {
if (wildcard_okay)
@@ -375,9 +384,9 @@ int config_param_host_port_wildcard (char *str, char **addr, char **port, int wi
}
// if (**port != NULL) free(*port);
*port = strdup(port_buf);
-
+
// printf("ADDR FINAL: '%s', '%s'\n", *addr, *port);
-
+
return 1;
}
@@ -394,12 +403,12 @@ int config_param_val_int_pos (char *str, int *dst) {
int num = 0;
if (str != NULL)
num = atoi(str);
-
+
if (num < 1) {
config_error_set("Not a positive number.");
return 0;
}
-
+
*dst = num;
return 1;
}
@@ -413,12 +422,12 @@ int config_param_val_intl_pos (char *str, long int *dst) {
long int num = 0;
if (str != NULL)
num = atol(str);
-
+
if (num < 1) {
config_error_set("Not a positive number.");
return 0;
}
-
+
*dst = num;
return 1;
}
@@ -451,7 +460,7 @@ int config_param_shcupd_mcastif (char *str, char **iface, char **ttl) {
*iface = str;
}
*ttl = sp + 1;
-
+
return 1;
}
@@ -460,7 +469,7 @@ int config_param_shcupd_peer (char *str, stud_config *cfg) {
config_error_set("Configuration pointer is NULL.");
return 0;
}
-
+
// parse result
int r = 1;
@@ -480,7 +489,7 @@ int config_param_shcupd_peer (char *str, stud_config *cfg) {
);
return 0;
}
-
+
// create place for new peer
char *addr = malloc(ADDR_LEN);
if (addr == NULL) {
@@ -502,13 +511,13 @@ int config_param_shcupd_peer (char *str, stud_config *cfg) {
goto outta_parse_peer;
}
memset(port, '\0', PORT_LEN);
-
+
// try to parse address
if (! config_param_host_port(str, &addr, &port)) {
r = 0;
goto outta_parse_peer;
}
-
+
outta_parse_peer:
if (! r) {
@@ -518,7 +527,7 @@ int config_param_shcupd_peer (char *str, stud_config *cfg) {
cfg->SHCUPD_PEERS[offset].ip = addr;
cfg->SHCUPD_PEERS[offset].port = port;
}
-
+
return r;
}
@@ -527,7 +536,7 @@ int config_param_shcupd_peer (char *str, stud_config *cfg) {
void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int line) {
int r = 1;
struct stat st;
-
+
if (strcmp(k, "tls") == 0) {
//cfg->ENC_TLS = 1;
}
@@ -603,7 +612,7 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
r = 0;
} else {
cfg->UID = passwd->pw_uid;
- cfg->GID = passwd->pw_gid;
+ cfg->GID = passwd->pw_gid;
}
}
}
@@ -615,7 +624,7 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
config_error_set("Invalid group '%s'.", v);
r = 0;
} else {
- cfg->GID = grp->gr_gid;
+ cfg->GID = grp->gr_gid;
}
}
}
@@ -675,6 +684,9 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
else if (strcmp(k, CFG_WRITE_PROXY) == 0) {
r = config_param_val_bool(v, &cfg->WRITE_PROXY_LINE);
}
+ else if (strcmp(k, CFG_PROXY_PROXY) == 0) {
+ r = config_param_val_bool(v, &cfg->PROXY_PROXY_LINE);
+ }
else if (strcmp(k, CFG_PEM_FILE) == 0) {
if (v != NULL && strlen(v) > 0) {
if (stat(v, &st) != 0) {
@@ -683,9 +695,13 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
}
else if (! S_ISREG(st.st_mode)) {
config_error_set("Invalid x509 certificate PEM file '%s': Not a file.", v);
- r = 0;
- } else
- config_assign_str(&cfg->CERT_FILE, v);
+ r = 0;
+ } else {
+ struct cert_files *cert = calloc(1, sizeof(*cert));
+ config_assign_str(&cert->CERT_FILE, v);
+ cert->NEXT = cfg->CERT_FILES;
+ cfg->CERT_FILES = cert;
+ }
}
}
else {
@@ -695,10 +711,10 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
k, file, line
);
}
-
+
if (! r) {
if (file != NULL)
- config_die("Error in configuration file '%s', line %d: %s\n", file, line, config_error_get());
+ config_die("Error in configuration file '%s', line %d: %s\n", file, line, config_error_get());
else
config_die("Invalid parameter '%s': %s", k, config_error_get());
}
@@ -708,7 +724,7 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
int config_file_parse (char *file, stud_config *cfg) {
if (cfg == NULL)
config_die("Undefined stud options; THIS IS A BUG!\n");
-
+
char line[CONFIG_BUF_SIZE];
FILE *fd = NULL;
@@ -727,25 +743,25 @@ int config_file_parse (char *file, stud_config *cfg) {
memset(line, '\0', sizeof(line));
if (fgets(line, (sizeof(line) - 1), fd) == NULL) break;
i++;
-
+
// get configuration key
char *key, *val;
key = config_get_param(line);
if (key == NULL) continue;
-
+
// get configuration key value...
val = config_get_value(line);
if (val == NULL) continue;
str_trim(val);
-
+
// printf("File '%s', line %d, key: '%s', value: '%s'\n", file, i, key, val);
-
+
// validate configuration key => value
config_param_validate(key, val, cfg, file, i);
}
fclose(fd);
-
+
return 1;
}
#endif /* NO_CONFIG_FILE */
@@ -775,7 +791,7 @@ char * config_disp_gid (gid_t gid) {
if (gr) {
memcpy(tmp_buf, gr->gr_name, strlen(gr->gr_name));
}
- return tmp_buf;
+ return tmp_buf;
}
char * config_disp_hostport (char *host, char *port) {
@@ -906,6 +922,9 @@ void config_print_usage_fd (char *prog, stud_config *cfg, FILE *out) {
fprintf(out, " --write-proxy Write HaProxy's PROXY (IPv4 or IPv6) protocol line\n" );
fprintf(out, " before actual data\n");
fprintf(out, " (Default: %s)\n", config_disp_bool(cfg->WRITE_PROXY_LINE));
+ fprintf(out, " --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line\n" );
+ fprintf(out, " before actual data\n");
+ fprintf(out, " (Default: %s)\n", config_disp_bool(cfg->PROXY_PROXY_LINE));
fprintf(out, "\n");
fprintf(out, " -t --test Test configuration and exit\n");
fprintf(out, " -V --version Print program version and exit\n");
@@ -938,11 +957,13 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "\n");
fprintf(fd, "# SSL x509 certificate file. REQUIRED.\n");
+ fprintf(fd, "# List multiple certs to use SNI. Certs are used in the order they\n");
+ fprintf(fd, "# are listed; the last cert listed will be used if none of the others match\n");
fprintf(fd, "#\n");
fprintf(fd, "# type: string\n");
- fprintf(fd, FMT_QSTR, CFG_PEM_FILE, config_disp_str(cfg->CERT_FILE));
+ fprintf(fd, FMT_QSTR, CFG_PEM_FILE, "");
fprintf(fd, "\n");
-
+
fprintf(fd, "# SSL protocol.\n");
fprintf(fd, "#\n");
fprintf(fd, "# tls = on\n");
@@ -992,14 +1013,14 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "# type: integer\n");
fprintf(fd, FMT_ISTR, CFG_SHARED_CACHE, cfg->SHARED_CACHE);
fprintf(fd, "\n");
-
+
fprintf(fd, "# Accept shared SSL cache updates on specified listener.\n");
fprintf(fd, "#\n");
fprintf(fd, "# type: string\n");
fprintf(fd, "# syntax: [HOST]:PORT\n");
fprintf(fd, FMT_QSTR, CFG_SHARED_CACHE_LISTEN, config_disp_hostport(cfg->SHCUPD_IP, cfg->SHCUPD_PORT));
fprintf(fd, "\n");
-
+
fprintf(fd, "# Shared cache peer address.\n");
fprintf(fd, "# Multiple stud processes on multiple hosts (host limit: %d)\n", MAX_SHCUPD_PEERS);
fprintf(fd, "# can share SSL session cache by sending updates to peers.\n");
@@ -1072,7 +1093,7 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "# Report client address by writing IP before sending data\n");
fprintf(fd, "#\n");
- fprintf(fd, "# NOTE: This option is mutually exclusive with option %s.\n", CFG_WRITE_PROXY);
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_PROXY, CFG_PROXY_PROXY);
fprintf(fd, "#\n");
fprintf(fd, "# type: boolean\n");
fprintf(fd, FMT_STR, CFG_WRITE_IP, config_disp_bool(cfg->WRITE_IP_OCTET));
@@ -1082,12 +1103,20 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "# http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt\n");
fprintf(fd, "# for details.\n");
fprintf(fd, "#\n");
- fprintf(fd, "# NOTE: This option is mutually exclusive with option %s.\n", CFG_WRITE_IP);
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_IP, CFG_PROXY_PROXY);
fprintf(fd, "#\n");
fprintf(fd, "# type: boolean\n");
fprintf(fd, FMT_STR, CFG_WRITE_PROXY, config_disp_bool(cfg->WRITE_PROXY_LINE));
fprintf(fd, "\n");
+ fprintf(fd, "# Proxy an existing SENDPROXY protocol header through this request.\n");
+ fprintf(fd, "#\n");
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_IP, CFG_WRITE_PROXY);
+ fprintf(fd, "#\n");
+ fprintf(fd, "# type: boolean\n");
+ fprintf(fd, FMT_STR, CFG_PROXY_PROXY, config_disp_bool(cfg->PROXY_PROXY_LINE));
+ fprintf(fd, "\n");
+
fprintf(fd, "# EOF\n");
}
#endif /* NO_CONFIG_FILE */
@@ -1099,7 +1128,7 @@ void config_print_usage (char *prog, stud_config *cfg) {
void config_parse_cli(int argc, char **argv, stud_config *cfg) {
static int tls = 0, ssl = 0;
static int client = 0;
- int c;
+ int c, i;
int test_only = 0;
char *prog;
@@ -1108,10 +1137,10 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
{ CFG_CONFIG, 1, NULL, CFG_PARAM_CFGFILE },
{ CFG_CONFIG_DEFAULT, 0, NULL, CFG_PARAM_DEFCFG },
#endif
-
+
{ "tls", 0, &tls, 1},
- { "ssl", 0, &ssl, 1},
- { "client", 0, &client, 1},
+ { "ssl", 0, &ssl, 1},
+ { "client", 0, &client, 1},
{ CFG_CIPHERS, 1, NULL, 'c' },
{ CFG_PREFER_SERVER_CIPHERS, 0, NULL, 'O' },
{ CFG_BACKEND, 1, NULL, 'b' },
@@ -1134,7 +1163,8 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
{ CFG_DAEMON, 0, &cfg->DAEMONIZE, 1 },
{ CFG_WRITE_IP, 0, &cfg->WRITE_IP_OCTET, 1 },
{ CFG_WRITE_PROXY, 0, &cfg->WRITE_PROXY_LINE, 1 },
-
+ { CFG_PROXY_PROXY, 0, &cfg->PROXY_PROXY_LINE, 1 },
+
{ "test", 0, NULL, 't' },
{ "version", 0, NULL, 'V' },
{ "help", 0, NULL, 'h' },
@@ -1197,7 +1227,7 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
config_param_validate(CFG_SHARED_CACHE_LISTEN, optarg, cfg, NULL, 0);
break;
case 'P':
- config_param_validate(CFG_SHARED_CACHE_PEER, optarg, cfg, NULL, 0);
+ config_param_validate(CFG_SHARED_CACHE_PEER, optarg, cfg, NULL, 0);
break;
case 'M':
config_param_validate(CFG_SHARED_CACHE_MCASTIF, optarg, cfg, NULL, 0);
@@ -1237,7 +1267,7 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
config_die("Invalid command line parameters. Run %s --help for instructions.", basename(argv[0]));
}
}
-
+
prog = argv[0];
if (tls && ssl)
@@ -1256,6 +1286,12 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
if (cfg->WRITE_IP_OCTET && cfg->WRITE_PROXY_LINE)
config_die("Options --write-ip and --write-proxy are mutually exclusive.");
+ if (cfg->WRITE_PROXY_LINE && cfg->PROXY_PROXY_LINE)
+ config_die("Options --write-proxy and --proxy-proxy are mutually exclusive.");
+
+ if (cfg->WRITE_IP_OCTET && cfg->PROXY_PROXY_LINE)
+ config_die("Options --write-ip and --proxy-proxy are mutually exclusive.");
+
if (cfg->DAEMONIZE) {
cfg->SYSLOG = 1;
cfg->QUIET = 1;
@@ -1265,20 +1301,23 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
if (cfg->SHCUPD_IP != NULL && ! cfg->SHARED_CACHE)
config_die("Shared cache update listener is defined, but shared cache is disabled.");
#endif
-
- // argv leftovers, do we have pem file as an argument?
+
+ // Any arguments left are presumed to be PEM files
argc -= optind;
argv += optind;
- if (argv != NULL && argv[0] != NULL)
- config_param_validate(CFG_PEM_FILE, argv[0], cfg, NULL, 0);
- else if ((cfg->PMODE == SSL_SERVER) && (cfg->CERT_FILE == NULL || strlen(cfg->CERT_FILE) < 1))
+ for (i = 0; i < argc; i++) {
+ config_param_validate(CFG_PEM_FILE, argv[i], cfg, NULL, 0);
+ }
+ if (cfg->PMODE == SSL_SERVER && cfg->CERT_FILES == NULL) {
config_die("No x509 certificate PEM file specified!");
-
+ }
+
// was this only a test?
if (test_only) {
- fprintf(stderr, "Trying to initialize SSL context with certificate '%s'\n", cfg->CERT_FILE);
- if (! init_openssl())
+ fprintf(stderr, "Trying to initialize SSL contexts with your certificates");
+ if (!init_openssl()) {
config_die("Error initializing OpenSSL.");
+ }
printf("%s configuration looks ok.\n", basename(prog));
exit(0);
}
View
8 configuration.h
@@ -31,12 +31,18 @@ typedef enum {
SSL_CLIENT
} PROXY_MODE;
+struct cert_files {
+ char *CERT_FILE;
+ struct cert_files *NEXT;
+};
+
/* configuration structure */
struct __stud_config {
ENC_TYPE ETYPE;
PROXY_MODE PMODE;
int WRITE_IP_OCTET;
int WRITE_PROXY_LINE;
+ int PROXY_PROXY_LINE;
char *CHROOT;
uid_t UID;
gid_t GID;
@@ -45,7 +51,7 @@ struct __stud_config {
char *BACK_IP;
char *BACK_PORT;
long NCORES;
- char *CERT_FILE;
+ struct cert_files *CERT_FILES;
char *CIPHER_SUITE;
char *ENGINE;
int BACKLOG;
View
29 shctx.c
@@ -1,15 +1,15 @@
/*
* shctx.c
*
- * Copyright (C) 2011 EXCELIANCE
- *
+ * Copyright (C) 2011 EXCELIANCE
+ *
* Author: Emeric Brun - emeric@exceliance.fr
*
*/
#include <sys/mman.h>
#ifdef USE_SYSCALL_FUTEX
#include <unistd.h>
-#include <linux/futex.h>
+#include <linux/futex.h>
#include <sys/syscall.h>
#else /* USE_SYSCALL_FUTEX */
#include <pthread.h>
@@ -157,7 +157,7 @@ static inline void shared_context_unlock(void)
#define shsess_set_key(s,k,l) { memcpy((s)->key_data, (k), (l)); \
if ((l) < SSL_MAX_SSL_SESSION_ID_LENGTH) \
- memset((s)->key_data+(l), 0, SSL_MAX_SSL_SESSION_ID_LENGTH-(l)); };
+ memset((s)->key_data+(l), 0, SSL_MAX_SSL_SESSION_ID_LENGTH-(l)); };
/* SSL context callbacks */
@@ -180,7 +180,7 @@ int shctx_new_cb(SSL *ssl, SSL_SESSION *sess) {
i2d_SSL_SESSION(sess, &p);
shared_context_lock();
-
+
shsess = shsess_get_next();
shsess_tree_delete(shsess);
@@ -193,7 +193,7 @@ int shctx_new_cb(SSL *ssl, SSL_SESSION *sess) {
/* store ASN1 encoded session into cache */
shsess->data_len = data_len;
memcpy(shsess->data, data, data_len);
-
+
/* store creation date */
shsess->c_date = SSL_SESSION_get_time(sess);
@@ -206,7 +206,7 @@ int shctx_new_cb(SSL *ssl, SSL_SESSION *sess) {
/* copy sessionid padded with 0 into the sessionid + data aligned buffer */
memcpy(encsess, sess->session_id, sess->session_id_length);
if (sess->session_id_length < SSL_MAX_SSL_SESSION_ID_LENGTH)
- memset(encsess+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sess->session_id_length);
+ memset(encsess+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sess->session_id_length);
shared_session_new_cbk(encsess, SSL_MAX_SSL_SESSION_ID_LENGTH+data_len, SSL_SESSION_get_time(sess));
}
@@ -230,7 +230,7 @@ SSL_SESSION *shctx_get_cb(SSL *ssl, unsigned char *key, int key_len, int *do_cop
/* tree key is zeros padded sessionid */
if ( key_len < SSL_MAX_SSL_SESSION_ID_LENGTH ) {
memcpy(tmpkey, key, key_len);
- memset(tmpkey+key_len, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-key_len);
+ memset(tmpkey+key_len, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-key_len);
key = tmpkey;
}
@@ -260,7 +260,7 @@ SSL_SESSION *shctx_get_cb(SSL *ssl, unsigned char *key, int key_len, int *do_cop
p = data;
sess = d2i_SSL_SESSION(NULL, (const unsigned char **)&p, data_len);
- /* reset creation date */
+ /* reset creation date */
if (sess)
SSL_SESSION_set_time(sess, cdate);
@@ -277,7 +277,7 @@ void shctx_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) {
/* tree key is zeros padded sessionid */
if ( sess->session_id_length < SSL_MAX_SSL_SESSION_ID_LENGTH ) {
memcpy(tmpkey, sess->session_id, sess->session_id_length);
- memset(tmpkey+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sess->session_id_length);
+ memset(tmpkey+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sess->session_id_length);
key = tmpkey;
}
@@ -303,7 +303,7 @@ void shctx_sess_add(const unsigned char *encsess, unsigned int len, long cdate)
|| (len > SHSESS_MAX_DATA_LEN+SSL_MAX_SSL_SESSION_ID_LENGTH) )
return;
-
+
shared_context_lock();
shsess = shsess_get_next();
@@ -334,7 +334,7 @@ void shsess_set_new_cbk(void (*func)(unsigned char *, unsigned int, long)) {
}
/* Init shared memory context if not allocated and set SSL context callbacks
- * size is the max number of stored session
+ * size is the max number of stored session
* Returns: -1 on alloc failure, size if performs context alloc, and 0 if just perform
* callbacks registration */
int shared_context_init(SSL_CTX *ctx, int size)
@@ -367,9 +367,9 @@ int shared_context_init(SSL_CTX *ctx, int size)
/* No duplicate authorized in tree: */
shctx->active.key.node.branches.b[1] = (void *)1;
- cur = &shctx->active;
+ cur = &shctx->active;
cur->n = cur->p = cur;
-
+
cur = &shctx->free;
for ( i = 0 ; i < size ; i++) {
prev = cur;
@@ -393,4 +393,3 @@ int shared_context_init(SSL_CTX *ctx, int size)
return ret;
}
-
View
432 stud.c
@@ -53,10 +53,13 @@
#include <sched.h>
#include <signal.h>
-#include <openssl/x509.h>
#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/x509.h>
#include <openssl/err.h>
#include <openssl/engine.h>
+#include <openssl/asn1.h>
#include <ev.h>
#include "ringbuffer.h"
@@ -80,6 +83,13 @@
# define SOL_TCP IPPROTO_TCP
#endif
+/* Do we have SNI support? */
+#ifndef OPENSSL_NO_TLSEXT
+#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
+#define OPENSSL_NO_TLSEXT
+#endif
+#endif
+
/* Globals */
static struct ev_loop *loop;
static struct addrinfo *backaddr;
@@ -88,7 +98,7 @@ static ev_io listener;
static int listener_socket;
static int child_num;
static pid_t *child_pids;
-static SSL_CTX *ssl_ctx;
+static SSL_CTX *default_ctx;
static SSL_SESSION *client_session;
#ifdef USE_SHARED_CACHE
@@ -96,12 +106,6 @@ static ev_io shcupd_listener;
static int shcupd_socket;
struct addrinfo *shcupd_peers[MAX_SHCUPD_PEERS+1];
static unsigned char shared_secret[SHA_DIGEST_LENGTH];
-
-//typedef struct shcupd_peer_opt {
-// const char *ip;
-// const char *port;
-//} shcupd_peer_opt;
-
#endif /*USE_SHARED_CACHE*/
long openssl_version;
@@ -118,58 +122,76 @@ typedef enum _SHUTDOWN_REQUESTOR {
SHUTDOWN_SSL
} SHUTDOWN_REQUESTOR;
+#ifndef OPENSSL_NO_TLSEXT
+/*
+ * SSL context linked list. Someday it might be nice to have a more clever data
+ * structure here, but assuming the number of SNI certs is small it probably
+ * doesn't matter.
+ */
+typedef struct ctx_list {
+ char *servername;
+ SSL_CTX *ctx;
+ struct ctx_list *next;
+} ctx_list;
+
+static ctx_list *sni_ctxs;
+
+#endif /* OPENSSL_NO_TLSEXT */
+
/*
* Proxied State
*
* All state associated with one proxied connection
*/
typedef struct proxystate {
- ringbuffer ring_ssl2clear; /* pushing bytes from secure to clear stream */
- ringbuffer ring_clear2ssl; /* pushing bytes from clear to secure stream */
+ ringbuffer ring_ssl2clear; /* Pushing bytes from secure to clear stream */
+ ringbuffer ring_clear2ssl; /* Pushing bytes from clear to secure stream */
- ev_io ev_r_ssl; /* secure stream write event */
- ev_io ev_w_ssl; /* secure stream read event */
+ ev_io ev_r_ssl; /* Secure stream write event */
+ ev_io ev_w_ssl; /* Secure stream read event */
- ev_io ev_r_handshake; /* secure stream handshake write event */
- ev_io ev_w_handshake; /* secure stream handshake read event */
+ ev_io ev_r_handshake; /* Secure stream handshake write event */
+ ev_io ev_w_handshake; /* Secure stream handshake read event */
- ev_io ev_w_connect; /* backend connect event */
+ ev_io ev_w_connect; /* Backend connect event */
- ev_io ev_r_clear; /* clear stream write event */
- ev_io ev_w_clear; /* clear stream read event */
+ ev_io ev_r_clear; /* Clear stream write event */
+ ev_io ev_w_clear; /* Clear stream read event */
- int fd_up; /* Upstream (client) socket */
- int fd_down; /* Downstream (backend) socket */
+ ev_io ev_proxy; /* proxy read event */
- int want_shutdown:1; /* Connection is half-shutdown */
- int handshaked:1; /* Initial handshake happened */
- int clear_connected:1; /* clear stream is connected */
- int renegotiation:1; /* Renegotation is occuring */
+ int fd_up; /* Upstream (client) socket */
+ int fd_down; /* Downstream (backend) socket */
- SSL *ssl; /* OpenSSL SSL state */
+ int want_shutdown:1; /* Connection is half-shutdown */
+ int handshaked:1; /* Initial handshake happened */
+ int clear_connected:1; /* Clear stream is connected */
+ int renegotiation:1; /* Renegotation is occuring */
+
+ SSL *ssl; /* OpenSSL SSL state */
struct sockaddr_storage remote_ip; /* Remote ip returned from `accept` */
} proxystate;
-#define LOG(...) \
- do { \
- if (!CONFIG->QUIET) fprintf(stdout, __VA_ARGS__); \
- if (CONFIG->SYSLOG) syslog(LOG_INFO, __VA_ARGS__); \
+#define LOG(...) \
+ do { \
+ if (!CONFIG->QUIET) fprintf(stdout, __VA_ARGS__); \
+ if (CONFIG->SYSLOG) syslog(LOG_INFO, __VA_ARGS__); \
} while(0)
-#define ERR(...) \
- do { \
- fprintf(stderr, __VA_ARGS__); \
- if (CONFIG->SYSLOG) syslog(LOG_ERR, __VA_ARGS__); \
+#define ERR(...) \
+ do { \
+ fprintf(stderr, __VA_ARGS__); \
+ if (CONFIG->SYSLOG) syslog(LOG_ERR, __VA_ARGS__); \
} while(0)
#define NULL_DEV "/dev/null"
-/* set a file descriptor (socket) to non-blocking mode */
+/* Set a file descriptor (socket) to non-blocking mode */
static void setnonblocking(int fd) {
int flag = 1;
- assert (ioctl(fd, FIONBIO, &flag) == 0);
+ assert(ioctl(fd, FIONBIO, &flag) == 0);
}
/* set a tcp socket to use TCP Keepalive */
@@ -196,12 +218,12 @@ static void fail(const char* s) {
}
void die (char *fmt, ...) {
- va_list args;
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
- exit(1);
+ exit(1);
}
#ifndef OPENSSL_NO_DH
@@ -213,8 +235,8 @@ static int init_dh(SSL_CTX *ctx, const char *cert) {
bio = BIO_new_file(cert, "r");
if (!bio) {
- ERR_print_errors_fp(stderr);
- return -1;
+ ERR_print_errors_fp(stderr);
+ return -1;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
@@ -233,7 +255,7 @@ static int init_dh(SSL_CTX *ctx, const char *cert) {
#ifdef NID_X9_62_prime256v1
EC_KEY *ecdh = NULL;
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
- SSL_CTX_set_tmp_ecdh(ctx,ecdh);
+ SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
LOG("{core} ECDH Initialized with NIST P-256\n");
#endif /* NID_X9_62_prime256v1 */
@@ -272,7 +294,7 @@ static void handle_shcupd(struct ev_loop *loop, ev_io *w, int revents) {
while ( ( r = recv(w->fd, msg, sizeof(msg), 0) ) > 0 ) {
/* msg len must be greater than 1 Byte of data + sig length */
- if (r < (int)(1+sizeof(shared_secret)))
+ if (r < (int)(1+sizeof(shared_secret)))
continue;
/* compute sig */
@@ -287,13 +309,13 @@ static void handle_shcupd(struct ev_loop *loop, ev_io *w, int revents) {
continue;
/* msg len must be greater than 1 Byte of data + encdate length */
- if (r < (int)(1+sizeof(uint32_t)))
+ if (r < (int)(1+sizeof(uint32_t)))
continue;
- /* drop too unsync updates */
+ /* drop too unsync updates */
r -= sizeof(uint32_t);
encdate = *((uint32_t *)&msg[r]);
- if (!(abs((int)(int32_t)now-ntohl(encdate)) < SSL_CTX_get_timeout(ssl_ctx)))
+ if (!(abs((int)(int32_t)now-ntohl(encdate)) < SSL_CTX_get_timeout(default_ctx)))
continue;
shctx_sess_add(msg, r, now);
@@ -370,7 +392,7 @@ static int create_shcupd_socket() {
}
int s = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP);
-
+
if (s == -1)
fail("{socket: shared cache updates}");
@@ -415,7 +437,7 @@ static int create_shcupd_socket() {
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) {
if (errno != EINVAL) { /* EINVAL if it is not a multicast address,
- not an error we consider unicast */
+ not an error we consider unicast */
fail("{setsockopt: IP_ADD_MEMBERSIP}");
}
}
@@ -475,7 +497,7 @@ static int create_shcupd_socket() {
if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
if (errno != EINVAL) { /* EINVAL if it is not a multicast address,
- not an error we consider unicast */
+ not an error we consider unicast */
fail("{setsockopt: IPV6_ADD_MEMBERSIP}");
}
}
@@ -485,7 +507,7 @@ static int create_shcupd_socket() {
if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) {
fail("{setsockopt: IPV6_MULTICAST_LOOP}");
}
- }
+ }
/* optional set sockopts for sending to multicast msg */
if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF,
@@ -521,100 +543,122 @@ RSA *load_rsa_privatekey(SSL_CTX *ctx, const char *file) {
bio = BIO_new_file(file, "r");
if (!bio) {
- ERR_print_errors_fp(stderr);
- return NULL;
+ ERR_print_errors_fp(stderr);
+ return NULL;
}
rsa = PEM_read_bio_RSAPrivateKey(bio, NULL,
ctx->default_passwd_callback, ctx->default_passwd_callback_userdata);
BIO_free(bio);
-
+
return rsa;
}
-/* Init library and load specified certificate.
- * Establishes a SSL_ctx, to act as a template for
- * each connection */
-SSL_CTX * init_openssl() {
- SSL_library_init();
- SSL_load_error_strings();
- SSL_CTX *ctx = NULL;
+#ifndef OPENSSL_NO_TLSEXT
+/*
+ * Switch the context of the current SSL object to the most appropriate one
+ * based on the SNI header
+ */
+int sni_switch_ctx(SSL *ssl, int *al, void *data) {
+ (void)data;
+ (void)al;
+ const char *servername;
+ const ctx_list *cl;
+
+ servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (!servername) return SSL_TLSEXT_ERR_NOACK;
+
+ // For now, just compare servernames as case insensitive strings. Someday,
+ // it might be nice to Do The Right Thing around star certs.
+ for (cl = sni_ctxs; cl != NULL; cl = cl->next) {
+ if (strcasecmp(servername, cl->servername) == 0) {
+ SSL_set_SSL_CTX(ssl, cl->ctx);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ }
+
+ return SSL_TLSEXT_ERR_NOACK;
+}
+#endif /* OPENSSL_NO_TLSEXT */
+
+
+/*
+ * Initialize an SSL context
+ */
+
+SSL_CTX *make_ctx(const char *pemfile) {
+ SSL_CTX *ctx;
RSA *rsa;
- long ssloptions = SSL_OP_NO_SSLv2 | SSL_OP_ALL |
+ long ssloptions = SSL_OP_NO_SSLv2 | SSL_OP_ALL |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
- if (CONFIG->ETYPE == ENC_TLS)
- ctx = SSL_CTX_new((CONFIG->PMODE == SSL_CLIENT) ? TLSv1_client_method() : TLSv1_server_method());
- else if (CONFIG->ETYPE == ENC_SSL)
- ctx = SSL_CTX_new((CONFIG->PMODE == SSL_CLIENT) ? SSLv23_client_method() : SSLv23_server_method());
- else
- assert(CONFIG->ETYPE == ENC_TLS || CONFIG->ETYPE == ENC_SSL);
-
#ifdef SSL_OP_NO_COMPRESSION
ssloptions |= SSL_OP_NO_COMPRESSION;
#endif
+ if (CONFIG->ETYPE == ENC_TLS) {
+ ctx = SSL_CTX_new((CONFIG->PMODE == SSL_CLIENT) ?
+ TLSv1_client_method() : TLSv1_server_method());
+ } else if (CONFIG->ETYPE == ENC_SSL) {
+ ctx = SSL_CTX_new((CONFIG->PMODE == SSL_CLIENT) ?
+ SSLv23_client_method() : SSLv23_server_method());
+ } else {
+ assert(CONFIG->ETYPE == ENC_TLS || CONFIG->ETYPE == ENC_SSL);
+ return NULL; // Won't happen, but gcc was complaining
+ }
+
SSL_CTX_set_options(ctx, ssloptions);
SSL_CTX_set_info_callback(ctx, info_callback);
- if (CONFIG->ENGINE) {
- ENGINE *e = NULL;
- ENGINE_load_builtin_engines();
- if (!strcmp(CONFIG->ENGINE, "auto"))
- ENGINE_register_all_complete();
- else {
- if ((e = ENGINE_by_id(CONFIG->ENGINE)) == NULL ||
- !ENGINE_init(e) ||
- !ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
- ERR_print_errors_fp(stderr);
- exit(1);
- }
- LOG("{core} will use OpenSSL engine %s.\n", ENGINE_get_id(e));
- ENGINE_finish(e);
- ENGINE_free(e);
+ if (CONFIG->CIPHER_SUITE) {
+ if (SSL_CTX_set_cipher_list(ctx, CONFIG->CIPHER_SUITE) != 1) {
+ ERR_print_errors_fp(stderr);
}
}
- if (CONFIG->CIPHER_SUITE)
- if (SSL_CTX_set_cipher_list(ctx, CONFIG->CIPHER_SUITE) != 1)
- ERR_print_errors_fp(stderr);
-
- if (CONFIG->PREFER_SERVER_CIPHERS)
+ if (CONFIG->PREFER_SERVER_CIPHERS) {
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
-
- if (CONFIG->PMODE == SSL_CLIENT)
+ if (CONFIG->PMODE == SSL_CLIENT) {
return ctx;
+ }
/* SSL_SERVER Mode stuff */
- if (SSL_CTX_use_certificate_chain_file(ctx, CONFIG->CERT_FILE) <= 0) {
+ if (SSL_CTX_use_certificate_chain_file(ctx, pemfile) <= 0) {
ERR_print_errors_fp(stderr);
exit(1);
}
- rsa = load_rsa_privatekey(ctx, CONFIG->CERT_FILE);
- if(!rsa) {
+ rsa = load_rsa_privatekey(ctx, pemfile);
+ if (!rsa) {
ERR("Error loading rsa private key\n");
exit(1);
}
- if (SSL_CTX_use_RSAPrivateKey(ctx,rsa) <= 0) {
+ if (SSL_CTX_use_RSAPrivateKey(ctx, rsa) <= 0) {
ERR_print_errors_fp(stderr);
exit(1);
}
#ifndef OPENSSL_NO_DH
- init_dh(ctx, CONFIG->CERT_FILE);
+ init_dh(ctx, pemfile);
#endif /* OPENSSL_NO_DH */
+#ifndef OPENSSL_NO_TLSEXT
+ if (!SSL_CTX_set_tlsext_servername_callback(ctx, sni_switch_ctx)) {
+ ERR("Error setting up SNI support\n");
+ }
+#endif /* OPENSSL_NO_TLSEXT */
+
#ifdef USE_SHARED_CACHE
if (CONFIG->SHARED_CACHE) {
if (shared_context_init(ctx, CONFIG->SHARED_CACHE) < 0) {
ERR("Unable to alloc memory for shared cache.\n");
exit(1);
}
- if (CONFIG->SHCUPD_PORT) {
+ if (CONFIG->SHCUPD_PORT) {
if (compute_secret(rsa, shared_secret) < 0) {
ERR("Unable to compute shared secret.\n");
exit(1);
@@ -634,6 +678,102 @@ SSL_CTX * init_openssl() {
return ctx;
}
+/* Init library and load specified certificate.
+ * Establishes a SSL_ctx, to act as a template for
+ * each connection */
+void init_openssl() {
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ assert(CONFIG->CERT_FILES != NULL);
+
+ // The first file (i.e., the last file listed in config) is always the
+ // "default" cert
+ default_ctx = make_ctx(CONFIG->CERT_FILES->CERT_FILE);
+
+#ifndef OPENSSL_NO_TLSEXT
+ {
+ struct cert_files *cf;
+ int i;
+ SSL_CTX *ctx;
+ X509 *x509;
+ BIO *f;
+
+ STACK_OF(GENERAL_NAME) *names = NULL;
+ GENERAL_NAME *name;
+
+#define PUSH_CTX(asn1_str, ctx) \
+ do { \
+ struct ctx_list *cl; \
+ cl = calloc(1, sizeof(*cl)); \
+ ASN1_STRING_to_UTF8((unsigned char **)&cl->servername, asn1_str); \
+ cl->ctx = ctx; \
+ cl->next = sni_ctxs; \
+ sni_ctxs = cl; \
+ } while (0)
+
+ // Go through the list of PEMs and make some SSL contexts for them. We also
+ // keep track of the names associated with each cert so we can do SNI on
+ // them later
+ for (cf = CONFIG->CERT_FILES->NEXT; cf != NULL; cf = cf->NEXT) {
+ ctx = make_ctx(cf->CERT_FILE);
+ f = BIO_new(BIO_s_file());
+ // TODO: error checking
+ if (!BIO_read_filename(f, cf->CERT_FILE)) {
+ ERR("Could not read cert '%s'\n", cf->CERT_FILE);
+ }
+ x509 = PEM_read_bio_X509_AUX(f, NULL, NULL, NULL);
+ BIO_free(f);
+
+ // First, look for Subject Alternative Names
+ names = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL);
+ for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+ name = sk_GENERAL_NAME_value(names, i);
+ if (name->type == GEN_DNS) {
+ PUSH_CTX(name->d.dNSName, ctx);
+ }
+ }
+ if (sk_GENERAL_NAME_num(names) > 0) {
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+ // If we actally found some, don't bother looking any further
+ continue;
+ } else if (names != NULL) {
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+ }
+
+ // Now we're left looking at the CN on the cert
+ X509_NAME *x509_name = X509_get_subject_name(x509);
+ i = X509_NAME_get_index_by_NID(x509_name, NID_commonName, -1);
+ if (i < 0) {
+ ERR("Could not find Subject Alternative Names or a CN on cert %s\n",
+ cf->CERT_FILE);
+ }
+ X509_NAME_ENTRY *x509_entry = X509_NAME_get_entry(x509_name, i);
+ PUSH_CTX(x509_entry->value, ctx);
+ }
+ }
+#undef APPEND_CTX
+#endif /* OPENSSL_NO_TLSEXT */
+
+ if (CONFIG->ENGINE) {
+ ENGINE *e = NULL;
+ ENGINE_load_builtin_engines();
+ if (!strcmp(CONFIG->ENGINE, "auto"))
+ ENGINE_register_all_complete();
+ else {
+ if ((e = ENGINE_by_id(CONFIG->ENGINE)) == NULL ||
+ !ENGINE_init(e) ||
+ !ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
+ ERR_print_errors_fp(stderr);
+ exit(1);
+ }
+ LOG("{core} will use OpenSSL engine %s.\n", ENGINE_get_id(e));
+ ENGINE_finish(e);
+ ENGINE_free(e);
+ }
+ }
+}
+
static void prepare_proxy_line(struct sockaddr* ai_addr) {
tcp_proxy_line[0] = 0;
char tcp6_address_string[INET6_ADDRSTRLEN];
@@ -651,10 +791,10 @@ static void prepare_proxy_line(struct sockaddr* ai_addr) {
struct sockaddr_in6* addr = (struct sockaddr_in6*)ai_addr;
inet_ntop(AF_INET6,&(addr->sin6_addr),tcp6_address_string,INET6_ADDRSTRLEN);
size_t res = snprintf(tcp_proxy_line,
- sizeof(tcp_proxy_line),
- "PROXY %%s %%s %s %%hu %hu\r\n",
- tcp6_address_string,
- ntohs(addr->sin6_port));
+ sizeof(tcp_proxy_line),
+ "PROXY %%s %%s %s %%hu %hu\r\n",
+ tcp6_address_string,
+ ntohs(addr->sin6_port));
assert(res < sizeof(tcp_proxy_line));
}
else {
@@ -678,7 +818,7 @@ static int create_main_socket() {
}
int s = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
-
+
if (s == -1)
fail("{socket: main}");
@@ -695,7 +835,7 @@ static int create_main_socket() {
#ifndef NO_DEFER_ACCEPT
#if TCP_DEFER_ACCEPT
- int timeout = 1;
+ int timeout = 1;
setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(int) );
#endif /* TCP_DEFER_ACCEPT */
#endif
@@ -744,6 +884,7 @@ static void shutdown_proxy(proxystate *ps, SHUTDOWN_REQUESTOR req) {
ev_io_stop(loop, &ps->ev_w_connect);
ev_io_stop(loop, &ps->ev_w_clear);
ev_io_stop(loop, &ps->ev_r_clear);
+ ev_io_stop(loop, &ps->ev_proxy);
close(ps->fd_up);
close(ps->fd_down);
@@ -947,7 +1088,7 @@ static void end_handshake(proxystate *ps) {
"TCP6",
tcp6_address_string,
ntohs(addr->sin6_port));
- }
+ }
ringbuffer_write_append(&ps->ring_ssl2clear, written);
}
else if (CONFIG->WRITE_IP_OCTET) {
@@ -966,7 +1107,7 @@ static void end_handshake(proxystate *ps) {
ringbuffer_write_append(&ps->ring_ssl2clear, 1U + 4U);
}
}
- /* start connect now */
+ /* start connect now */
start_connect(ps);
}
else {
@@ -988,6 +1129,47 @@ static void end_handshake(proxystate *ps) {
ev_io_start(loop, &ps->ev_w_ssl);
}
+static void client_proxy_proxy(struct ev_loop *loop, ev_io *w, int revents) {
+ (void) revents;
+ int t;
+ char *proxy = tcp_proxy_line, *end = tcp_proxy_line + sizeof(tcp_proxy_line);
+ proxystate *ps = (proxystate *)w->data;
+ BIO *b = SSL_get_rbio(ps->ssl);
+
+ // Copy characters one-by-one until we hit a \n or an error
+ while (proxy != end && (t = BIO_read(b, proxy, 1)) == 1) {
+ if (*proxy++ == '\n') break;
+ }
+
+ if (proxy == end) {
+ LOG("{client} Unexpectedly long PROXY line. Perhaps a malformed request?");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ }
+ else if (t == 1) {
+ if (ringbuffer_is_full(&ps->ring_ssl2clear)) {
+ LOG("{client} Error writing PROXY line");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ return;
+ }
+
+ char *ring = ringbuffer_write_ptr(&ps->ring_ssl2clear);
+ memcpy(ring, tcp_proxy_line, proxy - tcp_proxy_line);
+ ringbuffer_write_append(&ps->ring_ssl2clear, proxy - tcp_proxy_line);
+
+ // Finished reading the PROXY header
+ if (*(proxy - 1) == '\n') {
+ ev_io_stop(loop, &ps->ev_proxy);
+
+ // Start the real handshake
+ start_handshake(ps, SSL_ERROR_WANT_READ);
+ }
+ }
+ else if (!BIO_should_retry(b)) {
+ LOG("{client} Unexpected error reading PROXY line");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ }
+}
+
/* 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 */
@@ -1039,7 +1221,7 @@ static void handle_fatal_ssl_error(proxystate *ps, int err, int backend) {
* and buffer anything we get for writing to the backend */
static void ssl_read(struct ev_loop *loop, ev_io *w, int revents) {
(void) revents;
- int t;
+ int t;
proxystate *ps = (proxystate *)w->data;
if (ps->want_shutdown) {
ev_io_stop(loop, &ps->ev_r_ssl);
@@ -1190,6 +1372,8 @@ static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
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_proxy, client_proxy_proxy, client, EV_READ);
+
ev_io_init(&ps->ev_w_connect, handle_connect, back, EV_WRITE);
ev_io_init(&ps->ev_w_clear, clear_write, back, EV_WRITE);
@@ -1199,6 +1383,7 @@ static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
ps->ev_w_ssl.data = ps;
ps->ev_r_clear.data = ps;
ps->ev_w_clear.data = ps;
+ ps->ev_proxy.data = ps;
ps->ev_w_connect.data = ps;
ps->ev_r_handshake.data = ps;
ps->ev_w_handshake.data = ps;
@@ -1206,7 +1391,12 @@ static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
/* Link back proxystate to SSL state */
SSL_set_app_data(ssl, ps);
- start_handshake(ps, SSL_ERROR_WANT_READ); /* for client-first handshake */
+ if (CONFIG->PROXY_PROXY_LINE) {
+ ev_io_start(loop, &ps->ev_proxy);
+ }
+ else {
+ start_handshake(ps, SSL_ERROR_WANT_READ); /* for client-first handshake */
+ }
}
@@ -1350,7 +1540,7 @@ static void handle_connections() {
ev_timer_start(loop, &timer_ppid_check);
ev_io_init(&listener, (CONFIG->PMODE == SSL_CLIENT) ? handle_clear_accept : handle_accept, listener_socket, EV_READ);
- listener.data = ssl_ctx;
+ listener.data = default_ctx;
ev_io_start(loop, &listener);
ev_loop(loop, 0);
@@ -1444,7 +1634,7 @@ void start_children(int start_index, int count) {
void replace_child_with_pid(pid_t pid) {
int i;
- /* find old child's slot and put a new child there */
+ /* find old child's slot and put a new child there */
for (i = 0; i < CONFIG->NCORES; i++) {
if (child_pids[i] == pid) {
start_children(i, 1);
@@ -1601,12 +1791,12 @@ void openssl_check_version() {
/* compiled with */
if ((openssl_version ^ OPENSSL_VERSION_NUMBER) & ~0xff0L) {
ERR(
- "WARNING: {core} OpenSSL version mismatch; stud was compiled with %lx, now using %lx.\n",
- (unsigned long int) OPENSSL_VERSION_NUMBER,
- (unsigned long int) openssl_version
- );
- /* now what? exit now? */
- /* exit(1); */
+ "WARNING: {core} OpenSSL version mismatch; stud was compiled with %lx, now using %lx.\n",
+ (unsigned long int) OPENSSL_VERSION_NUMBER,
+ (unsigned long int) openssl_version
+ );
+ /* now what? exit now? */
+ /* exit(1); */
}
LOG("{core} Using OpenSSL version %lx.\n", (unsigned long int) openssl_version);
@@ -1617,10 +1807,10 @@ void openssl_check_version() {
int main(int argc, char **argv) {
// initialize configuration
CONFIG = config_new();
-
+
// parse command line
config_parse_cli(argc, argv, CONFIG);
-
+
create_workers = 1;
openssl_check_version();
@@ -1635,12 +1825,12 @@ int main(int argc, char **argv) {
if (CONFIG->SHCUPD_PORT) {
/* create socket to send(children) and
receive(parent) cache updates */
- shcupd_socket = create_shcupd_socket();
+ shcupd_socket = create_shcupd_socket();
}
#endif /* USE_SHARED_CACHE */
- /* load certificate, pass to handle_connections */
- ssl_ctx = init_openssl();
+ /* load certificates, pass to handle_connections */
+ init_openssl();
if (CONFIG->CHROOT && CONFIG->CHROOT[0])
change_root();
@@ -1670,7 +1860,7 @@ int main(int argc, char **argv) {
ev_io_init(&shcupd_listener, handle_shcupd, shcupd_socket, EV_READ);
ev_io_start(loop, &shcupd_listener);
-
+
ev_loop(loop, 0);
}
#endif /* USE_SHARED_CACHE */
@@ -1680,6 +1870,6 @@ int main(int argc, char **argv) {
* Parent will be woken up if a signal arrives */
pause();
}
-
+
exit(0); /* just a formality; we never get here */
}
Something went wrong with that request. Please try again.