proxytunnel/ptstream.c
2024-01-30 19:47:34 +01:00

427 lines
11 KiB
C

/* Proxytunnel - (C) 2001-2008 Jos Visser / Mark Janssen */
/* Contact: josv@osp.nl / maniac@maniac.nl */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* ptstream.c */
#include <arpa/inet.h>
#include <openssl/x509v3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include "proxytunnel.h"
/* Open a stream for incoming and outgoing data with the specified fds */
PTSTREAM *stream_open(int incoming_fd, int outgoing_fd) {
PTSTREAM *pts;
/* Initialise the structure and store the file descriptor */
pts = malloc(sizeof(PTSTREAM));
pts->incoming_fd = incoming_fd;
pts->outgoing_fd = outgoing_fd;
pts->ssl = NULL;
pts->ctx = NULL;
/* Return a pointer to the structure */
return pts;
}
/* Close a stream */
int stream_close(PTSTREAM *pts) {
#ifdef USE_SSL
/* Destroy the SSL context */
if (pts->ssl) {
SSL_shutdown (pts->ssl);
SSL_free (pts->ssl);
SSL_CTX_free (pts->ctx);
}
#endif /* USE_SSL */
/* Close the incoming fd */
close(pts->incoming_fd);
/* Close the outgoing fd */
close(pts->outgoing_fd);
/* Free the structure */
free(pts);
return 1;
}
/* Read from a stream */
int stream_read(PTSTREAM *pts, void *buf, size_t len) {
/* Read up to the specified number of bytes into the buffer */
int bytes_read;
if (!pts->ssl) {
/* For a non-SSL stream... */
bytes_read = read(pts->incoming_fd, buf, len);
} else {
#ifdef USE_SSL
/* For an SSL stream... */
bytes_read = SSL_read(pts->ssl, buf, len);
#else
/* No SSL support, so must use a non-SSL stream */
bytes_read = read(pts->incoming_fd, buf, len);
#endif /* USE_SSL */
}
return bytes_read;
}
/* Write to a stream */
int stream_write(PTSTREAM *pts, void *buf, size_t len) {
/* Write the specified number of bytes from the buffer */
int bytes_written;
int total_bytes_written = 0;
while (total_bytes_written < len) {
if (!pts->ssl) {
/* For a non-SSL stream... */
bytes_written = write(pts->outgoing_fd,
buf + total_bytes_written,
len - total_bytes_written);
} else {
#ifdef USE_SSL
/* For an SSL stream... */
bytes_written = SSL_write(pts->ssl,
buf + total_bytes_written,
len - total_bytes_written);
#else
/* No SSL support, so must use a non-SSL stream */
bytes_written = write(pts->outgoing_fd,
buf + total_bytes_written,
len - total_bytes_written);
#endif /* USE_SSL */
}
if (bytes_written <= 0) {
break;
}
total_bytes_written += bytes_written;
}
return total_bytes_written;
}
/*
* Copy a block of data from one stream to another. A true
* return code signifies EOF on the from socket descriptor.
*/
int stream_copy(PTSTREAM *pts_from, PTSTREAM *pts_to) {
char buf[SIZE];
int n;
/* Read a buffer from the source socket */
if ( ( n = stream_read( pts_from, buf, SIZE ) ) < 0 ) {
my_perror( "Socket read error" );
exit( 1 );
}
/* If we have read 0 bytes, there is an EOF on src */
if( n==0 )
return 1;
/* Write the buffer to the destination socket */
if ( stream_write( pts_to, buf, n ) != n ) {
my_perror( "Socket write error" );
exit( 1 );
}
/* We're not yet at EOF */
return 0;
}
/* Check the certificate host name against the expected host name */
/* Return 1 if peer hostname is valid, any other value indicates failure */
int check_cert_valid_host(const char *cert_host, const char *peer_host) {
if (cert_host == NULL || peer_host == NULL) {
return 0;
}
if (cert_host[0] == '*') {
if (strncmp(cert_host, "*.", 2) != 0) {
/* Invalid wildcard hostname */
return 0;
}
/* Skip "*." */
cert_host += 2;
/* Wildcards can only match the first subdomain component */
while (*peer_host++ != '.' && *peer_host != '\0')
;;
}
if (strlen(cert_host) == 0 || strlen(peer_host) == 0) {
return 0;
}
return strcmp(cert_host, peer_host) == 0;
}
int check_cert_valid_ip6(const unsigned char *cert_ip_data, const int cert_ip_len, const struct in6_addr *addr6) {
int i;
for (i = 0; i < cert_ip_len; i++) {
if (cert_ip_data[i] != addr6->s6_addr[i]) {
return 0;
}
}
return 1;
}
int check_cert_valid_ip(const unsigned char *cert_ip_data, const int cert_ip_len, const struct in_addr *addr) {
int i;
for (i = 0; i < cert_ip_len; i++) {
if (cert_ip_data[i] != ((addr->s_addr >> (i * 8)) & 0xFF)) {
return 0;
}
}
return 1;
}
int check_cert_names(X509 *cert, char *peer_host) {
char peer_cn[256];
const GENERAL_NAME *gn;
STACK_OF(GENERAL_NAME) *gen_names;
struct in_addr addr;
struct in6_addr addr6;
int peer_host_is_ipv4 = 0, peer_host_is_ipv6 = 0;
int i, san_count;
gen_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
san_count = sk_GENERAL_NAME_num(gen_names);
if (san_count > 0) {
peer_host_is_ipv4 = (inet_pton(AF_INET, peer_host, &addr) == 1);
peer_host_is_ipv6 = (peer_host_is_ipv4 ? 0 : inet_pton(AF_INET6, peer_host, &addr6) == 1);
for (i = 0; i < san_count; i++) {
gn = sk_GENERAL_NAME_value(gen_names, i);
if (gn->type == GEN_DNS && !(peer_host_is_ipv4 || peer_host_is_ipv6)) {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (check_cert_valid_host((char*)ASN1_STRING_get0_data(gn->d.ia5), peer_host)) {
#else
if (check_cert_valid_host((char*)ASN1_STRING_data(gn->d.ia5), peer_host)) {
#endif
return 1;
}
} else if (gn->type == GEN_IPADD) {
if (gn->d.ip->length == 4 && peer_host_is_ipv4) {
if (check_cert_valid_ip(gn->d.ip->data, gn->d.ip->length, &addr)) {
return 1;
}
} else if (gn->d.ip->length == 16 && peer_host_is_ipv6) {
if (check_cert_valid_ip6(gn->d.ip->data, gn->d.ip->length, &addr6)) {
return 1;
}
}
}
}
message("Host name %s does not match certificate subject alternative names\n", peer_host);
} else {
X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, peer_cn, sizeof(peer_cn));
message("Host name %s does not match certificate common name %s or any subject alternative names\n", peer_host, peer_cn);
return check_cert_valid_host(peer_cn, peer_host);
}
return 0;
}
/* Initiate an SSL handshake on this stream and encrypt all subsequent data */
int stream_enable_ssl(PTSTREAM *pts, const char *proxy_arg) {
#ifdef USE_SSL
const SSL_METHOD *meth;
SSL *ssl;
SSL_CTX *ctx;
long res = 1;
long ssl_options = 0;
X509* cert = NULL;
int status;
struct stat st_buf;
#ifndef DEFAULT_CA_FILE
const char *ca_file = NULL;
#else
const char *ca_file = DEFAULT_CA_FILE; /* Default cert file from Makefile */
#endif /* !DEFAULT_CA_FILE */
#ifndef DEFAULT_CA_DIR
const char *ca_dir = "/etc/ssl/certs/"; /* Default cert directory if none given */
#else
const char *ca_dir = DEFAULT_CA_DIR; /* Default cert directory from Makefile */
#endif /* !DEFAULT_CA_DIR */
long vresult;
const char *peer_arg = NULL;
size_t peer_arg_len;
char peer_arg_fmt[32];
char *peer_host = NULL;
/* Initialise the connection */
SSLeay_add_ssl_algorithms();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
meth = TLS_client_method();
#else
meth = SSLv23_client_method();
#endif
SSL_load_error_strings();
ctx = SSL_CTX_new (meth);
ssl_options |= SSL_OP_NO_SSLv3;
SSL_CTX_set_options (ctx, ssl_options);
if ( !args_info.no_check_cert_flag ) {
if ( args_info.cacert_given ) {
if ((status = stat(args_info.cacert_arg, &st_buf)) != 0) {
message("Error reading certificate path %s\n", args_info.cacert_arg);
goto fail;
}
if (S_ISDIR(st_buf.st_mode)) {
ca_dir = args_info.cacert_arg;
} else {
ca_dir = NULL;
ca_file = args_info.cacert_arg;
}
}
if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_dir)) {
message("Error loading certificate(s) from %s\n", args_info.cacert_arg);
goto fail;
}
}
/* If given, load client certificate (chain) and key */
if ( args_info.clientcert_given && args_info.clientkey_given ) {
if ( 1 != SSL_CTX_use_certificate_chain_file(ctx, args_info.clientcert_arg) ) {
message("Error loading client certificate (chain) from %s\n", args_info.clientcert_arg);
goto fail;
}
if ( 1 != SSL_CTX_use_PrivateKey_file(ctx, args_info.clientkey_arg, SSL_FILETYPE_PEM) ) {
message("Error loading client key from %s, or key does not match certificate\n", args_info.clientkey_arg);
goto fail;
}
}
ssl = SSL_new (ctx);
if ( ssl == NULL ) {
message("SSL_new failed\n");
goto fail;
}
SSL_set_rfd (ssl, stream_get_incoming_fd(pts));
SSL_set_wfd (ssl, stream_get_outgoing_fd(pts));
/* Determine the host name we are connecting to */
peer_arg = args_info.host_given ? args_info.host_arg : proxy_arg;
peer_arg_len = strlen(peer_arg);
peer_host = alloca(peer_arg_len + 1);
snprintf( peer_arg_fmt, sizeof(peer_arg_fmt), peer_arg[0] == '[' ? "[%%%zu[^]]]" : "%%%zu[^:]", peer_arg_len);
if ( sscanf( peer_arg, peer_arg_fmt, peer_host ) != 1 ) {
goto fail;
}
/* SNI support */
if ( args_info.verbose_flag ) {
message( "Set SNI hostname to %s\n", peer_host);
}
res = SSL_set_tlsext_host_name(ssl, peer_host);
if ( res != 1 ) {
message( "SSL_set_tlsext_host_name() failed for host name '%s'. "
"TLS SNI error, giving up\n", peer_host);
goto fail;
}
if ( SSL_connect (ssl) <= 0) {
message( "SSL_connect failed\n");
goto fail;
}
if ( !args_info.no_check_cert_flag ) {
/* Make sure peer presented a certificate */
cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
message("No certificate presented\n");
goto fail;
}
/* Check that the certificate is valid */
vresult = SSL_get_verify_result(ssl);
if (vresult != X509_V_OK) {
message("Certificate verification failed (%s)\n",
X509_verify_cert_error_string(vresult));
goto fail;
}
/* Verify the certificate name matches the host we are connecting to */
if (!check_cert_names(cert, peer_host)) {
goto fail;
}
X509_free(cert);
}
/* Store ssl and ctx parameters */
pts->ssl = ssl;
pts->ctx = ctx;
#else
message("Warning: stream_open(): SSL stream requested but no SSL support available; using unencrypted connection");
#endif /* USE_SSL */
return 1;
fail:
#ifdef USE_SSL
if (cert != NULL) {
X509_free(cert);
}
#endif /* USE_SSL */
exit(1);
}
/* Return the incoming_fd for a given stream */
int stream_get_incoming_fd(PTSTREAM *pts) {
if (!pts->ssl)
return pts->incoming_fd;
else
#ifdef USE_SSL
return SSL_get_rfd(pts->ssl);
#else
return pts->incoming_fd;
#endif /* USE_SSL */
}
/* Return the outgoing_fd for a given stream */
int stream_get_outgoing_fd(PTSTREAM *pts) {
if (!pts->ssl)
return pts->outgoing_fd;
else
#ifdef USE_SSL
return SSL_get_wfd(pts->ssl);
#else
return pts->outgoing_fd;
#endif /* USE_SSL */
}
// vim:noexpandtab:ts=4