/*
* Copyright (c) 1999 - 2002 Peter 'Luna' Runestig <peter@runestig.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modifi-
* cation, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright no-
* tice, this list of conditions and the following disclaimer in the do-
* cumentation and/or other materials provided with the distribution.
*
* o The names of the contributors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``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 THE REGENTS OR CONTRIBUTORS BE LI-
* ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
* TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
* ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
* LITY, 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) Peter 'Luna' Runestig 1999 - 2002 <peter@runestig.com>.\n";
#endif /* not lint */
#if !defined(PR_OBSD_FTP) && !defined(PR_TELNET)
#error You must define PR_OBSD_FTP or PR_TELNET !
#endif
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif /* HAVE_NETDB_H */
#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/bio.h>
#ifdef TLS_SESSION_FILE_CACHE
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <openssl/pem.h>
#endif /* TLS_SESSION_FILE_CACHE */
#ifdef PR_TELNET
#include "ring.h"
#include "externs.h"
#define MODE_EDIT 0x01
void NetNonblockingIO(int fd, int onoff);
extern int net; /* the socket */
extern int tin; /* stdin */
extern char *hostname;
FILE *ttyout;
#define SOCK_TO_SSL(s) \
( tls_active ? ssl : NULL )
#endif /* PR_TELNET */
#ifdef PR_OBSD_FTP
# ifdef __STDC__
# include <stdarg.h>
# else
# include <varargs.h>
# endif
typedef struct _CONN {
SSL *ssl;
int sock;
} CONN;
#define SOCK_TO_SSL(s) \
( s == data_conn.sock ? data_conn.ssl : \
( s == ctrl_conn.sock ? ctrl_conn.ssl : NULL ) )
extern FILE *ttyout; /* stdout or stderr, depending on interactive */
#endif /* PR_OBSD_FTP */
/* define if you want to check for OpenSSL-related memory leaks */
/* #define DEBUG_OPENSSL_MEM */
#define FPUTC_BUFFERSIZE 1024
#define DEFAULTCIPHERLIST "HIGH:MEDIUM:LOW:+KRB5:+ADH:+EXP"
void tls_cleanup(void);
void tls_fputc_fflush(int fd);
int x509rc_read_filenames(void);
static unsigned char fputc_buffer[FPUTC_BUFFERSIZE];
static int fputc_buflen = 0;
static int verify_error_flag = 0;
static int x509rc_override = 0;
static int tls_nozlib = 0;
char *tls_key_file = NULL;
char *tls_cert_file = NULL;
char *tls_capath_dir = NULL;
char *tls_cafile_file = NULL;
char *tls_crl_file = NULL;
char *tls_crl_dir = NULL;
char *tls_rand_file = NULL;
char *tls_hostname = NULL; /* hostname used by user to connect */
char *tls_cipher_list = NULL;
static SSL_CTX *ssl_ctx = NULL;
static X509_STORE *crl_store = NULL;
static SSL_METHOD *tls_method = NULL;
#ifdef PR_TELNET
int tls_active = 0;
int tls_suspend_iacs = 0;
int tls_reset_state = 0;
static SSL *ssl = NULL;
#endif /* PR_TELNET */
#ifdef PR_OBSD_FTP
CONN data_conn = { NULL, -1 }, ctrl_conn = { NULL, -1 };
SSL *ctrl_ssl_clone = NULL;
int tls_want_on_ctrl = 1;
int tls_want_on_data = 0;
int tls_no_verify = 0;
#endif /* PR_OBSD_FTP */
void tls_set_cipher_list(char *list)
{
if (tls_cipher_list)
free(tls_cipher_list);
tls_cipher_list = strdup(list);
}
#ifdef PR_OBSD_FTP
int tls_active(int s)
{
if (SOCK_TO_SSL(s))
return 1;
else
return 0;
}
#endif /* PR_OBSD_FTP */
char *tls_get_cipher_info_string(int fd)
{
static char r[60];
SSL *s = SOCK_TO_SSL(fd);
if (s)
snprintf(r, sizeof(r), "cipher %s (%d bits)", SSL_get_cipher(s),
SSL_get_cipher_bits(s, NULL));
else
snprintf(r, sizeof(r), "clear");
return r;
}
void tls_set_defaults(void)
{
#ifdef ZLIB /* compression doesn't work with SSLv23_client_method() */
tls_method = SSLv3_client_method();
#else
tls_method = SSLv23_client_method();
#endif /* !ZLIB */
if (!tls_cipher_list)
tls_cipher_list = strdup(DEFAULTCIPHERLIST);
}
int tls_optarg(char *optarg)
{
char *p;
if ((p = strchr(optarg, '='))) {
*p++ = 0;
if (!strcmp(optarg, "cert")) {
tls_cert_file = strdup(p);
x509rc_override = 1;
}
else if (!strcmp(optarg, "key"))
tls_key_file = strdup(p);
else if (!strcmp(optarg, "CAfile"))
tls_cafile_file = strdup(p);
else if (!strcmp(optarg, "CApath"))
tls_capath_dir = strdup(p);
else if (!strcmp(optarg, "crlfile"))
tls_crl_file = strdup(p);
else if (!strcmp(optarg, "crldir"))
tls_crl_dir = strdup(p);
else if (!strcmp(optarg, "cipher"))
tls_set_cipher_list(p);
else
return 1;
}
else if (!strcmp(optarg, "sslv3"))
tls_method = SSLv3_client_method();
else if (!strcmp(optarg, "tlsv1"))
tls_method = TLSv1_client_method();
else if (!strcmp(optarg, "nozlib"))
tls_nozlib = 1;
#ifdef PR_OBSD_FTP
else if (!strcmp(optarg, "noprotect"))
tls_want_on_ctrl = 0;
else if (!strcmp(optarg, "private"))
tls_want_on_data = 1;
else if (!strcmp(optarg, "certsok"))
tls_no_verify = 1;
#endif /* PR_OBSD_FTP */
else
return 1;
return 0;
}
char **tls_get_SAN_objs(SSL *s, int type)
/* returns NULL or an array of malloc'ed objects of type `type' from the server's
* subjectAltName, remember to free() them all!
*/
{
#define NUM_SAN_OBJS 50
static char *objs[NUM_SAN_OBJS];
char **rv = NULL;
X509 *server_cert = NULL;
int i, j;
X509_EXTENSION *ext = NULL;
STACK_OF(GENERAL_NAME) *ialt = NULL;
GENERAL_NAME *gen = NULL;
memset(objs, 0, sizeof(objs));
if ((server_cert = SSL_get_peer_certificate(s))) {
if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0)
goto eject;
if (!(ext = X509_get_ext(server_cert, i)))
goto eject;
if (!(ialt = X509V3_EXT_d2i(ext)))
goto eject;
rv = objs;
for (i = 0, j = 0; i < sk_GENERAL_NAME_num(ialt) && j < NUM_SAN_OBJS - 2; i++) {
gen = sk_GENERAL_NAME_value(ialt, i);
if (gen->type == type) {
if(!gen->d.ia5 || !gen->d.ia5->length)
continue;
objs[j] = malloc(gen->d.ia5->length + 1);
if (objs[j]) {
memcpy(objs[j], gen->d.ia5->data, gen->d.ia5->length);
objs[j][gen->d.ia5->length] = 0;
j++;
}
}
GENERAL_NAME_free(gen);
}
}
eject:
if (ialt) sk_GENERAL_NAME_free(ialt);
if (server_cert) X509_free(server_cert);
return rv;
}
char *x509v3_subjectAltName_oneline(SSL *s, char *buf, int len)
{
X509 *server_cert = NULL;
X509_EXTENSION *ext = NULL;
BIO *mem = NULL;
char *data = NULL, *rv = NULL;
if ((server_cert = SSL_get_peer_certificate(s))) {
int i, data_len = 0, ok;
if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0)
goto eject;
if (!(ext = X509_get_ext(server_cert, i)))
goto eject;
if (!(mem = BIO_new(BIO_s_mem())))
goto eject;
ok = X509V3_EXT_print(mem, ext, 0, 0);
if (ok)
data_len = BIO_get_mem_data(mem, &data);
if (data) {
/* the 'data' returned is not '\0' terminated */
if (buf) {
memcpy(buf, data, data_len < len ? data_len : len);
buf[data_len < len ? data_len : len - 1] = 0;
rv = buf;
goto eject;
} else {
char *b = malloc(data_len + 1);
if (b) {
memcpy(b, data, data_len);
b[data_len] = 0;
}
rv = b;
goto eject;
}
} else
goto eject;
}
eject:
if (server_cert) X509_free(server_cert);
if (mem) BIO_free(mem);
return rv;
}
char *tls_get_commonName(SSL *s)
{
static char name[256];
int err = 0;
X509 *server_cert;
if ((server_cert = SSL_get_peer_certificate(s))) {
err = X509_NAME_get_text_by_NID(X509_get_subject_name(server_cert),
NID_commonName, name, sizeof(name));
X509_free(server_cert);
}
if (err > 0)
return name;
else
return NULL;
}
/* if we are using OpenSSL 0.9.6 or newer, we want to use X509_NAME_print_ex()
* instead of X509_NAME_oneline().
*/
char *x509_name_oneline(X509_NAME *n, char *buf, int len)
{
#if OPENSSL_VERSION_NUMBER < 0x000906000
return X509_NAME_oneline(n, buf, len);
#else
BIO *mem = BIO_new(BIO_s_mem());
char *data = NULL;
int data_len = 0, ok;
ok = X509_NAME_print_ex(mem, n, 0, XN_FLAG_ONELINE);
if (ok)
data_len = BIO_get_mem_data(mem, &data);
if (data) {
/* the 'data' returned is not '\0' terminated */
if (buf) {
memcpy(buf, data, data_len < len ? data_len : len);
buf[data_len < len ? data_len : len - 1] = 0;
BIO_free(mem);
return buf;
} else {
char *b = malloc(data_len + 1);
if (b) {
memcpy(b, data, data_len);
b[data_len] = 0;
}
BIO_free(mem);
return b;
}
} else {
BIO_free(mem);
return NULL;
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x000906000 */
}
char *tls_get_issuer_name(SSL *s)
{
static char name[256];
X509 *server_cert;
if ((server_cert = SSL_get_peer_certificate(s))) {
char *n = x509_name_oneline(X509_get_issuer_name(server_cert), name, sizeof(name));
X509_free(server_cert);
return n;
}
else
/* no certificate from server */
return NULL;
}
char *tls_get_subject_name(SSL *s)
{
static char name[256];
X509 *server_cert;
if ((server_cert = SSL_get_peer_certificate(s))) {
char *n = x509_name_oneline(X509_get_subject_name(server_cert), name, sizeof(name));
X509_free(server_cert);
return n;
}
else
/* no certificate from server */
return NULL;
}
static char read_char(void)
{
char inl[10];
#ifdef PR_TELNET
/* we must add MODE_EDIT for the fgets(), or CR won't terminate the line */
int mode = getconnmode();
TerminalNewMode(mode | MODE_EDIT);
NetNonblockingIO(tin, 0);
fgets(inl, sizeof(inl), stdin);
NetNonblockingIO(tin, 1);
TerminalNewMode(mode);
#else
fgets(inl, sizeof(inl), stdin);
#endif /* !PR_TELNET */
return *inl;
}
/* this one is (very much!) based on work by Ralf S. Engelschall <rse@engelschall.com>.
* comments by Ralf.
*/
static int verify_crl(int ok, X509_STORE_CTX *ctx)
{
X509_OBJECT obj;
X509_NAME *subject;
X509_NAME *issuer;
X509 *xs;
X509_CRL *crl;
X509_REVOKED *revoked;
X509_STORE_CTX store_ctx;
long serial;
int i, n, rc;
char *cp;
/*
* Unless a revocation store for CRLs was created we
* cannot do any CRL-based verification, of course.
*/
if (!crl_store)
return ok;
/*
* Determine certificate ingredients in advance
*/
xs = X509_STORE_CTX_get_current_cert(ctx);
subject = X509_get_subject_name(xs);
issuer = X509_get_issuer_name(xs);
/*
* OpenSSL provides the general mechanism to deal with CRLs but does not
* use them automatically when verifying certificates, so we do it
* explicitly here. We will check the CRL for the currently checked
* certificate, if there is such a CRL in the store.
*
* We come through this procedure for each certificate in the certificate
* chain, starting with the root-CA's certificate. At each step we've to
* both verify the signature on the CRL (to make sure it's a valid CRL)
* and it's revocation list (to make sure the current certificate isn't
* revoked). But because to check the signature on the CRL we need the
* public key of the issuing CA certificate (which was already processed
* one round before), we've a little problem. But we can both solve it and
* at the same time optimize the processing by using the following
* verification scheme (idea and code snippets borrowed from the GLOBUS
* project):
*
* 1. We'll check the signature of a CRL in each step when we find a CRL
* through the _subject_ name of the current certificate. This CRL
* itself will be needed the first time in the next round, of course.
* But we do the signature processing one round before this where the
* public key of the CA is available.
*
* 2. We'll check the revocation list of a CRL in each step when
* we find a CRL through the _issuer_ name of the current certificate.
* This CRLs signature was then already verified one round before.
*
* This verification scheme allows a CA to revoke its own certificate as
* well, of course.
*/
/*
* Try to retrieve a CRL corresponding to the _subject_ of
* the current certificate in order to verify it's integrity.
*/
memset((char *)&obj, 0, sizeof(obj));
X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL);
rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
X509_STORE_CTX_cleanup(&store_ctx);
crl = obj.data.crl;
if (rc > 0 && crl != NULL) {
/*
* Verify the signature on this CRL
*/
if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) {
fprintf(stderr, "Invalid signature on CRL!\r\n");
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
X509_OBJECT_free_contents(&obj);
return 0;
}
/*
* Check date of CRL to make sure it's not expired
*/
i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
if (i == 0) {
fprintf(stderr, "Found CRL has invalid nextUpdate field.\r\n");
X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
X509_OBJECT_free_contents(&obj);
return 0;
}
if (i < 0) {
fprintf(stderr, "Found CRL is expired - revoking all certificates until you get updated CRL.\r\n");
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
X509_OBJECT_free_contents(&obj);
return 0;
}
X509_OBJECT_free_contents(&obj);
}
/*
* Try to retrieve a CRL corresponding to the _issuer_ of
* the current certificate in order to check for revocation.
*/
memset((char *)&obj, 0, sizeof(obj));
X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL);
rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
X509_STORE_CTX_cleanup(&store_ctx);
crl = obj.data.crl;
if (rc > 0 && crl != NULL) {
/*
* Check if the current certificate is revoked by this CRL
*/
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
for (i = 0; i < n; i++) {
revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) {
serial = ASN1_INTEGER_get(revoked->serialNumber);
cp = x509_name_oneline(issuer, NULL, 0);
fprintf(stderr,
"Certificate with serial %ld (0x%lX) revoked per CRL from issuer %s\r\n",
serial, serial, cp ? cp : "(ERROR)");
if (cp) free(cp);
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
X509_OBJECT_free_contents(&obj);
return 0;
}
}
X509_OBJECT_free_contents(&obj);
}
return ok;
}
static void print_x509_v_error(int error)
{
switch (error) {
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
fprintf(ttyout, "WARNING: Server's certificate is self signed.\r\n");
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
fprintf(ttyout, "WARNING: Server's certificate has expired.\r\n");
break;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
fprintf(ttyout,
"WARNING: Server's certificate issuer's certificate isn't available locally.\r\n");
break;
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
fprintf(ttyout, "WARNING: Unable to verify leaf signature.\r\n");
break;
case X509_V_ERR_CERT_REVOKED:
fprintf(ttyout, "WARNING: Certificate revoked.\r\n");
break;
case X509_V_ERR_INVALID_CA:
fprintf(ttyout, "WARNING: Invalid CA.\r\n");
break;
case X509_V_ERR_CRL_HAS_EXPIRED:
fprintf(ttyout, "WARNING: CRL has expired.\r\n");
break;
case X509_V_ERR_CERT_UNTRUSTED:
fprintf(ttyout, "WARNING: Certificate is untrusted.\r\n");
break;
default:
fprintf(ttyout,
"WARNING: Error %d while verifying server's certificate.\r\n", error);
break;
}
}
static int verify_callback(int ok, X509_STORE_CTX *ctx)
{
int prev_error = 0;
/*fprintf(stderr, "depth = %d, error = %d, ok = %d\n", ctx->error_depth, ctx->error, ok);*/
/* TODO: Make up my mind on what errors to accept or not. */
#ifdef PR_OBSD_FTP
/* we can configure the client to skip the peer's cert verification */
if (tls_no_verify)
return 1;
#endif /* PR_OBSD_FTP */
if (!ok) {
verify_error_flag = 1;
print_x509_v_error(prev_error = ctx->error);
}
/* since the CRL check isn't included in the OpenSSL automatic certificate
* check, we must call verify_crl() after we first check what errors the
* automatic check might have found, otherwise they might be lost.
*/
ok = verify_crl(ok, ctx);
if (!ok) {
verify_error_flag = 1;
if (ctx->error != prev_error)
print_x509_v_error(ctx->error);
}
ok = 1;
return ok;
}
static int seed_PRNG(void)
{
char stackdata[1024];
static char rand_file[300];
FILE *fh;
#if OPENSSL_VERSION_NUMBER >= 0x00905100
if (RAND_status())
return 0; /* PRNG already good seeded */
#endif
/* if the device '/dev/urandom' is present, OpenSSL uses it by default.
* check if it's present, else we have to make random data ourselfs.
*/
if ((fh = fopen("/dev/urandom", "r"))) {
fclose(fh);
return 0;
}
if (RAND_file_name(rand_file, sizeof(rand_file)))
tls_rand_file = rand_file;
else
return 1;
if (!RAND_load_file(rand_file, 1024)) {
/* no .rnd file found, create new seed */
unsigned int c;
c = time(NULL);
RAND_seed(&c, sizeof(c));
c = getpid();
RAND_seed(&c, sizeof(c));
RAND_seed(stackdata, sizeof(stackdata));
}
#if OPENSSL_VERSION_NUMBER >= 0x00905100
if (!RAND_status())
return 2; /* PRNG still badly seeded */
#endif
return 0;
}
#ifdef TLS_SESSION_FILE_CACHE
static char *sfc_filename = NULL;
static char *peer_ip_as_string(int sock)
{
#ifdef INET6
struct sockaddr_storage saddr;
#else
struct sockaddr_in saddr;
#endif /* !INET6 */
int saddr_len = sizeof(saddr);
static char rv[50];
if (getpeername(sock, (struct sockaddr *) &saddr, &saddr_len) != 0)
return NULL;
switch (((struct sockaddr *)&saddr)->sa_family) {
case AF_INET:
snprintf(rv, sizeof(rv), "%08X",
htonl(((struct sockaddr_in *)&saddr)->sin_addr.s_addr));
break;
#ifdef INET6
case AF_INET6:
snprintf(rv, sizeof(rv),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[0],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[1],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[2],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[3],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[4],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[5],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[6],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[7],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[8],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[9],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[10],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[11],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[12],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[13],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[14],
((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[15]);
break;
#endif /* INET6 */
default:
return NULL;
}
return rv;
}
static char *make_sfc_filename(int sock)
{
DIR *dir;
char path[MAXPATHLEN], *home, *peer;
static char filename[MAXPATHLEN];
home = getenv("HOME");
if (home == NULL)
return NULL;
snprintf(path, sizeof(path), "%s/.tls_sfc", home);
dir = opendir(path);
/* if the ~/.tls_sfc dir doesn't exist, we consider this function disabled */
if (dir == NULL)
return NULL;
closedir(dir);
/* the file name is based on the hexadecimal representation of the peer's
* ip address, with `oss' (from `OpenSsl Session') as a prefix.
* example: connected to 127.0.0.1 -> `oss7F000001'
*/
if ((peer = peer_ip_as_string(sock)) == NULL)
return NULL;
snprintf(filename, sizeof(filename), "%s/oss%s", path, peer);
return filename;
}
static int session_timed_out(SSL_SESSION *s)
{
if (SSL_SESSION_get_time(s) + SSL_SESSION_get_timeout(s) < time(NULL))
return 1;
else
return 0;
}
int tls_sfc_client_load(SSL *s)
{
FILE *file;
if (s == NULL)
return 1;
sfc_filename = make_sfc_filename(net);
if (sfc_filename == NULL)
return 2;
file = fopen(sfc_filename, "r");
if (file) {
SSL_SESSION *sess;
fchmod(fileno(file), S_IRUSR | S_IWUSR);
sess = PEM_read_SSL_SESSION(file, NULL, NULL, NULL);
if (sess) {
/* ``refresh'' the session timeout */
/*XXX SSL_SESSION_set_time(sess, time(NULL)); */
if (session_timed_out(sess))
unlink(sfc_filename);
else
SSL_set_session(s, sess);
/* dec the ref counter in sess so it will eventually be freed */
SSL_SESSION_free(sess);
}
fclose(file);
}
return 0;
}
int tls_sfc_client_save(SSL *s)
{
FILE *file;
if (s == NULL)
return 1;
if (sfc_filename == NULL)
return 2;
file = fopen(sfc_filename, "w");
if (file) {
SSL_SESSION *sess;
sess = SSL_get_session(s);
if (sess)
PEM_write_SSL_SESSION(file, sess);
fclose(file);
}
return 0;
}
#endif /* TLS_SESSION_FILE_CACHE */
int tls_init(void)
{
int err;
#ifndef PR_OBSD_FTP
ttyout = stderr;
#endif /* !PR_OBSD_FTP */
#ifdef DEBUG_OPENSSL_MEM
CRYPTO_malloc_debug_init();
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
#endif /* DEBUG_OPENSSL_MEM */
SSL_load_error_strings();
SSL_library_init();
#ifdef ZLIB
{
COMP_METHOD *cm = COMP_zlib();
if (!tls_nozlib && cm != NULL && cm->type != NID_undef) {
SSL_COMP_add_compression_method(0xe0, cm); /* Eric Young's ZLIB ID */
}
}
#endif /* ZLIB */
ssl_ctx = SSL_CTX_new(tls_method);
if (!ssl_ctx) {
fprintf(stderr, "SSL_CTX_new() %s\r\n",
(char *)ERR_error_string(ERR_get_error(), NULL));
return 1;
}
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
/* set up the CApath if defined */
if (tls_capath_dir || tls_cafile_file) {
printf("Setting verify path \n");
if (!SSL_CTX_load_verify_locations(ssl_ctx, tls_cafile_file, tls_capath_dir)) {
fprintf(stderr,"WARNING: can't set CApath/CAfile verify locations\n");
}
}
else
SSL_CTX_set_default_verify_paths(ssl_ctx);
#ifdef PR_OBSD_FTP
/* set up session caching */
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT);
SSL_CTX_set_session_id_context(ssl_ctx, (const unsigned char *) "1", 1);
#endif /* PR_OBSD_FTP */
/* set up the CRL */
if ((tls_crl_file || tls_crl_dir) && (crl_store = X509_STORE_new()))
X509_STORE_load_locations(crl_store, tls_crl_file, tls_crl_dir);
#ifdef PR_TELNET
ssl = SSL_new(ssl_ctx);
if (!ssl) {
fprintf(stderr, "SSL_new() %s\r\n",
(char *)ERR_error_string(ERR_get_error(), NULL));
return 5;
}
SSL_set_cipher_list(ssl, tls_cipher_list);
SSL_set_fd(ssl, net);
#endif /* PR_TELNET */
#ifdef TLS_SESSION_FILE_CACHE
/* I would love to hear the story on why I must use the
* SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option to get this working...
*/
SSL_set_options(ssl, SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG);
tls_sfc_client_load(ssl);
#endif /* TLS_SESSION_FILE_CACHE */
if (seed_PRNG())
fprintf(stderr, "Wasn't able to properly seed the PRNG!\r\n");
return 0;
}
static int show_hostname_warning(char *s1, char *s2)
{
char inp;
fprintf(stderr,
"WARNING: Hostname (\"%s\") and server's certificate (\"%s\") don't match, continue? (Y/N) ",
s1, s2);
inp = read_char();
if (!( inp == 'y' || inp == 'Y' ))
return 1;
else
return 0;
}
static int star_stricmp(const char *str, const char *star_str)
/* wildcard compares string `str' with a pattern string `star_str' which may
* contain ONE wildcard star, '*', case-insensitive. there's probably better
* ways to do this...
*/
{
char *str_copy = strdup(str);
char *star_str_copy = strdup(star_str);
char *star;
int rv = -1;
if (str_copy == NULL || star_str_copy == NULL)
goto eject;
star = strchr(star_str_copy, '*');
if (star) {
int str_len = strlen(str_copy);
int star_str_len = strlen(star_str_copy);
int star_idx = star - star_str_copy;
/* first check a few special cases */
if (star_str_len > str_len + 1)
/* `star_str' is too long to ever match */
goto eject;
else if (star_str_len == str_len + 1) {
if (*star_str_copy == '*')
/* possible "*foo" == "foo" case */
memmove(star_str_copy, star_str_copy + 1, star_str_len);
else if (star_str_copy[star_str_len - 1] == '*')
/* possible "foo*" == "foo" case */
star_str_copy[star_str_len - 1] = '\0';
else {
/* possible "f*oo" == "foo" case */
memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1,
star_str_len - star_idx);
}
} else {
int diff = str_len - star_str_len;
/* remove the chars from `str_copy' that the star ``wildcards'' */
memmove(str_copy + star_idx, str_copy + star_idx + diff + 1,
str_len - star_idx - diff);
/* remove the '*' from `star_str_copy' */
memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1,
star_str_len - star_idx);
}
}
rv = strcasecmp(str_copy, star_str_copy);
eject:
if (str_copy) free(str_copy);
if (star_str_copy) free(star_str_copy);
return rv;
}
static int dNSName_cmp(const char *host, const char *dNSName)
{
int c1 = 0, c2 = 0, num_comp, rv = -1;
char *p, *p1, *p2, *host_copy, *dNSName_copy;
/* first we count the number of domain name components in both parameters.
* they should be equal many, or it's not a match
*/
p = (char *) host;
while ((p = strchr(p, '.'))) {
c1++;
p++;
}
p = (char *) dNSName;
while ((p = strchr(p, '.'))) {
c2++;
p++;
}
if (c1 != c2)
return -1;
num_comp = c1 + 1;
host_copy = strdup(host);
dNSName_copy = strdup(dNSName);
if (host_copy == NULL || dNSName_copy == NULL)
goto eject;
/* make substrings by replacing '.' with '\0' */
p = dNSName_copy;
while ((p = strchr(p, '.'))) {
*p = '\0';
p++;
}
p = host_copy;
while ((p = strchr(p, '.'))) {
*p = '\0';
p++;
}
/* compare each component */
p1 = host_copy;
p2 = dNSName_copy;
for (; num_comp; num_comp--) {
if (star_stricmp(p1, p2))
/* failed match */
goto eject;
p1 += strlen(p1) + 1;
p2 += strlen(p2) + 1;
}
/* match ok */
rv = 0;
eject:
if (dNSName_copy) free(dNSName_copy);
if (host_copy) free(host_copy);
return rv;
}
#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif /* !INADDR_NONE */
static int check_server_name(SSL *s)
/* returns 0 if hostname and server's cert matches, else 1 */
{
char **dNSName, *commonName;
unsigned char **ipAddress;
struct in_addr ia;
/* first we check if `tls_hostname' is in fact an ip address */
if ((ia.s_addr = inet_addr(tls_hostname)) != INADDR_NONE) {
ipAddress = (unsigned char **) tls_get_SAN_objs(s, GEN_IPADD);
if (ipAddress) {
int i = 0, rv;
char *server_ip = "UNKNOWN";
for (i = 0; ipAddress[i]; i++)
if (*(unsigned long *)ipAddress[i] == ia.s_addr)
return 0;
if (ipAddress[i - 1]) {
ia.s_addr = *(unsigned long *)ipAddress[i - 1];
server_ip = inet_ntoa(ia);
}
rv = show_hostname_warning(tls_hostname, server_ip);
for (i = 0; ipAddress[i]; i++)
free(ipAddress[i]);
return rv;
} else
return show_hostname_warning(tls_hostname, "NO IP IN CERT");
}
/* look for dNSName(s) in subjectAltName in the server's certificate */
dNSName = tls_get_SAN_objs(s, GEN_DNS);
if (dNSName) {
int i = 0, rv;
for (i = 0; dNSName[i]; i++) {
if (!dNSName_cmp(tls_hostname, dNSName[i]))
return 0;
}
rv = show_hostname_warning(tls_hostname, dNSName[i - 1] ? dNSName[i - 1] : "UNKNOWN");
for (i = 0; dNSName[i]; i++)
free(dNSName[i]);
return rv;
} else if ((commonName = tls_get_commonName(s))) {
/* so the server didn't have any dNSName's, check the commonName */
if (!dNSName_cmp(tls_hostname, commonName))
return 0;
else
return show_hostname_warning(tls_hostname, commonName);
} else
return 1;
}
static char *file_fullpath(char *fn)
{
static char fp[256];
FILE *file;
char *dir;
/* check if it is a full path already */
if (strchr(fn, '/'))
return fn;
/* check if it is in current dir */
if ((file = fopen(fn, "r"))) {
fclose(file);
return fn;
}
if (!(dir = getenv(X509_get_default_cert_dir_env()))) /* $SSL_CERT_DIR */
dir = (char *)X509_get_default_cert_dir();
snprintf(fp, sizeof(fp), "%s/%s", dir, fn);
if ((file = fopen(fp, "r"))) {
fclose(file);
return fp;
}
dir = (char *)X509_get_default_private_dir();
snprintf(fp, sizeof(fp), "%s/%s", dir, fn);
if ((file = fopen(fp, "r"))) {
fclose(file);
return fp;
}
return fn; /* here fn is proven wrong, but we return it anyway */
}
#ifdef PR_TELNET
void tls_shutdown(void)
{
if (tls_active) {
SSL_shutdown(ssl);
tls_active = 0;
}
}
int tls_connect(void)
{
int err;
char *subject, *issuer, *subjectAltName, inp;
/* let's see if we are going to use any client certificate */
x509rc_read_filenames();
if (tls_cert_file) {
int mode;
char *key_file = tls_key_file;
if (!key_file)
key_file = tls_cert_file;
err = SSL_use_certificate_file(ssl, file_fullpath(tls_cert_file),
SSL_FILETYPE_PEM);
if (err <= 0) {
fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n",
file_fullpath(tls_cert_file),
(char *)ERR_error_string(ERR_get_error(), NULL));
return 1;
}
/* we must add MODE_EDIT for any password reading routine, or CR won't
* terminate the line */
mode = getconnmode();
TerminalNewMode(mode | MODE_EDIT);
err = SSL_use_PrivateKey_file(ssl, file_fullpath(key_file),
SSL_FILETYPE_PEM);
TerminalNewMode(mode);
if (err <= 0) {
fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n",
file_fullpath(key_file),
(char *)ERR_error_string(ERR_get_error(), NULL));
return 2;
}
if (!SSL_check_private_key(ssl)) {
fprintf(stderr, "Private key don't match the certificate public key!\r\n");
return 3;
}
}
/* it seems SSL_connect() don't like non-blocking sockets, or...? */
NetNonblockingIO(net, 0);
fprintf(stderr, "[Negotiating SSL/TLS session... ]\r\n");
err = SSL_connect(ssl);
NetNonblockingIO(net, 1);
if (err == 1) {
if (verify_error_flag) {
fprintf(stderr,
"WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) ");
inp = read_char();
if (!( inp == 'y' || inp == 'Y' ))
quit();
}
tls_active = 1;
if (subject = tls_get_subject_name(ssl))
fprintf(stderr, "[Subject: %s]\r\n", subject);
else {
fprintf(stderr,
"WARNING: Server didn't provide a certificate, continue? (Y/N) ");
inp = read_char();
if (!( inp == 'y' || inp == 'Y' ))
quit();
}
if ((subjectAltName = x509v3_subjectAltName_oneline(ssl, NULL, 0))) {
fprintf(stderr, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName);
free(subjectAltName);
}
if (issuer = tls_get_issuer_name(ssl))
fprintf(stderr, "[Issuer: %s]\r\n", issuer);
fprintf(stderr, "[Cipher: %s (%d bits)]\r\n", SSL_get_cipher(ssl),
SSL_get_cipher_bits(ssl, NULL));
if (ssl->expand && ssl->expand->meth)
fprintf(stderr, "Compression: %s\r\n", ssl->expand->meth->name);
if (check_server_name(ssl)) {
/* the host name on the command line didn't match with the server's
* cert, and the user didn't ansver `Y' to the question.
*/
quit();
}
return 0;
}
else { /* TLS connection failed */
fprintf(stderr, "SSL_connect() = %d, %s\r\n", err,
(char *)ERR_error_string(ERR_get_error(), NULL));
tls_shutdown();
tls_cleanup();
return 5;
}
return 6;
}
int tls_pending(void)
{
if (tls_active)
return SSL_pending(ssl);
else
return 0;
}
#endif /* PR_TELNET */
#ifdef PR_OBSD_FTP
int tls_connect_ctrl(int s)
{
int err;
char *subject, *issuer, *commonName, *dNSName, *subjectAltName, inp;
if (ctrl_conn.ssl) {
fprintf(ttyout, "Already TLS connected!\r\n");
return 1;
}
ctrl_conn.ssl = SSL_new(ssl_ctx);
if (!ctrl_conn.ssl) {
fprintf(ttyout, "SSL_new() %s\r\n",
(char *)ERR_error_string(ERR_get_error(), NULL));
return 2;
}
SSL_set_cipher_list(ctrl_conn.ssl, tls_cipher_list);
SSL_set_fd(ctrl_conn.ssl, s);
ctrl_conn.sock = s;
/* let's see if we are going to use any client certificate */
x509rc_read_filenames();
if (tls_cert_file) {
char *key_file = tls_key_file;
if (!key_file)
key_file = tls_cert_file;
err = SSL_use_certificate_file(ctrl_conn.ssl, file_fullpath(tls_cert_file),
SSL_FILETYPE_PEM);
if (err <= 0) {
fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n",
file_fullpath(tls_cert_file),
(char *)ERR_error_string(ERR_get_error(), NULL));
return 3;
}
err = SSL_use_PrivateKey_file(ctrl_conn.ssl, file_fullpath(key_file),
SSL_FILETYPE_PEM);
if (err <= 0) {
fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n",
file_fullpath(key_file),
(char *)ERR_error_string(ERR_get_error(), NULL));
return 4;
}
if (!SSL_check_private_key(ctrl_conn.ssl)) {
fprintf(stderr, "Private key don't match the certificate public key!\r\n");
return 5;
}
}
fprintf(ttyout, "[Starting SSL/TLS negotiation...]\r\n");
err = SSL_connect(ctrl_conn.ssl);
if (err == 1) {
if (verify_error_flag) {
fprintf(ttyout,
"WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) ");
inp = read_char();
if (!( inp == 'y' || inp == 'Y' ))
return 3;
}
if ((subject = tls_get_subject_name(ctrl_conn.ssl)))
fprintf(ttyout, "[Subject: %s]\r\n", subject);
else {
fprintf(ttyout,
"WARNING: Server didn't provide a certificate, continue? (Y/N) ");
inp = read_char();
if (!( inp == 'y' || inp == 'Y' ))
return 4;
}
if ((subjectAltName = x509v3_subjectAltName_oneline(ctrl_conn.ssl, NULL, 0))) {
fprintf(ttyout, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName);
free(subjectAltName);
}
if ((issuer = tls_get_issuer_name(ctrl_conn.ssl)))
fprintf(ttyout, "[Issuer: %s]\r\n", issuer);
fprintf(ttyout, "[Cipher: %s (%d bits)]\r\n", SSL_get_cipher(ctrl_conn.ssl),
SSL_get_cipher_bits(ctrl_conn.ssl, NULL));
if (ctrl_conn.ssl->expand && ctrl_conn.ssl->expand->meth)
fprintf(stderr, "Compression: %s\r\n", ctrl_conn.ssl->expand->meth->name);
if (check_server_name(ctrl_conn.ssl)) {
/* the host name on the command line didn't match with the server's
* cert, and the user didn't ansver `Y' to the question.
*/
return 5;
}
return 0;
}
else { /* TLS connection failed */
fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err,
(char *)ERR_error_string(ERR_get_error(), NULL));
SSL_shutdown(ctrl_conn.ssl);
SSL_free(ctrl_conn.ssl);
ctrl_conn.ssl = NULL;
ctrl_conn.sock = -1;
return 6;
}
}
int tls_connect_data(int s)
{
int err;
if (data_conn.ssl) {
fprintf(ttyout, "Already TLS connected!\r\n");
return 1;
}
data_conn.ssl = SSL_new(ssl_ctx);
if (!data_conn.ssl) {
fprintf(ttyout, "SSL_new() %s\r\n",
(char *)ERR_error_string(ERR_get_error(), NULL));
return 2;
}
/*
* if we have cleared the control connection back to
* plaintext (with CCC) then we need to pop the
* cloned copy instead of copying the, now missing,
* control connection
* N.B. if both ctrl_conn.ssl and ctrl_ssl_clone are NULL
* then something is broken.
*/
if((NULL == ctrl_conn.ssl) && (NULL != ctrl_ssl_clone))
{
SSL_copy_session_id(data_conn.ssl, ctrl_ssl_clone);
}
else
{
SSL_copy_session_id(data_conn.ssl, ctrl_conn.ssl);
}
SSL_set_fd(data_conn.ssl, s);
data_conn.sock = s;
err = SSL_connect(data_conn.ssl);
if (err != 1) { /* TLS connection failed */
fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err,
(char *)ERR_error_string(ERR_get_error(), NULL));
SSL_shutdown(data_conn.ssl);
SSL_free(data_conn.ssl);
data_conn.ssl = NULL;
data_conn.sock = -1;
return 3;
} else
return 0;
}
static void ssl_close(SSL *ssl)
{
if (ssl) {
if (ssl == data_conn.ssl) {
int fd = SSL_get_fd(ssl);
if (fd >= 0)
tls_fputc_fflush(fd);
/*if(NULL != ctrl_ssl_clone)
{
SSL_copy_session_id(ctrl_ssl_clone,data_conn.ssl);
}*/
}
SSL_shutdown(ssl);
SSL_free(ssl);
if (ssl == data_conn.ssl) {
data_conn.ssl = NULL;
data_conn.sock = -1;
} else if (ssl == ctrl_conn.ssl) {
ctrl_conn.ssl = NULL;
ctrl_conn.sock = -1;
}
}
}
int tls_fclose(FILE *stream)
{
SSL *ssl = SOCK_TO_SSL(fileno(stream));
ssl_close(ssl);
return fclose(stream);
}
int tls_close(int fd)
{
SSL *ssl = SOCK_TO_SSL(fd);
ssl_close(ssl);
return close(fd);
}
int tls_shutdown(int s, int how)
{
/* if s == -1, do a `global' SSL shutdown, else emulate a shutdown(2) */
if (s == -1) {
ssl_close(data_conn.ssl);
ssl_close(ctrl_conn.ssl);
return 0;
} else {
SSL *ssl = SOCK_TO_SSL(s);
ssl_close(ssl);
return shutdown(s, how);
}
}
void tls_free_ssls(void)
{
ssl_close(data_conn.ssl);
ssl_close(ctrl_conn.ssl);
}
#endif /* PR_OBSD_FTP */
void tls_cleanup(void)
{
if (crl_store) {
X509_STORE_free(crl_store);
crl_store = NULL;
}
#ifdef PR_TELNET
if (ssl) {
#ifdef TLS_SESSION_FILE_CACHE
tls_sfc_client_save(ssl);
#endif /* TLS_SESSION_FILE_CACHE */
SSL_free(ssl);
ssl = NULL;
}
#endif /* PR_TELNET */
#ifdef PR_OBSD_FTP
if (data_conn.ssl) {
SSL_free(data_conn.ssl);
data_conn.ssl = NULL;
data_conn.sock = -1;
}
if (ctrl_conn.ssl) {
SSL_free(ctrl_conn.ssl);
ctrl_conn.ssl = NULL;
ctrl_conn.sock = -1;
}
#endif /* PR_OBSD_FTP */
if (ssl_ctx) {
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
}
ERR_free_strings();
ERR_remove_state(0);
EVP_cleanup(); /* release the stuff allocated by SSL_library_init() */
if (tls_cert_file) {
free(tls_cert_file);
tls_cert_file = NULL;
}
if (tls_key_file) {
free(tls_key_file);
tls_key_file = NULL;
}
if (tls_crl_file) {
free(tls_crl_file);
tls_crl_file = NULL;
}
if (tls_crl_dir) {
free(tls_crl_dir);
tls_crl_dir = NULL;
}
if (tls_capath_dir) {
free(tls_capath_dir);
tls_capath_dir = NULL;
}
if (tls_cafile_file) {
free(tls_cafile_file);
tls_cafile_file = NULL;
}
if (tls_hostname) {
free(tls_hostname);
tls_hostname = NULL;
}
if (tls_cipher_list) {
free(tls_cipher_list);
tls_cipher_list = NULL;
}
if (tls_rand_file)
RAND_write_file(tls_rand_file);
#ifdef DEBUG_OPENSSL_MEM
CRYPTO_mem_leaks_fp(stderr);
#endif /* DEBUG_OPENSSL_MEM */
}
static void handle_ssl_error(int error, char *where)
{
switch (error) {
case SSL_ERROR_NONE:
return;
case SSL_ERROR_SSL:
fprintf(ttyout, "unhandled SSL_ERROR_SSL in %s\r\n", where);
break;
case SSL_ERROR_WANT_READ:
fprintf(ttyout, "unhandled SSL_ERROR_WANT_READ in %s\r\n", where);
break;
case SSL_ERROR_WANT_WRITE:
fprintf(ttyout, "unhandled SSL_ERROR_WANT_WRITE in %s\r\n", where);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
fprintf(ttyout, "unhandled SSL_ERROR_WANT_X509_LOOKUP in %s\r\n", where);
break;
case SSL_ERROR_SYSCALL:
fprintf(ttyout, "unhandled SSL_ERROR_SYSCALL in %s\r\n", where);
break;
case SSL_ERROR_ZERO_RETURN:
fprintf(ttyout, "unhandled SSL_ERROR_ZERO_RETURN in %s\r\n", where);
break;
case SSL_ERROR_WANT_CONNECT:
fprintf(ttyout, "unhandled SSL_ERROR_WANT_CONNECT in %s\r\n", where);
break;
default:
fprintf(ttyout, "unhandled SSL_ERROR %d in %s\r\n", error, where);
break;
}
#ifdef PR_OBSD_FTP
tls_shutdown(-1, 0);
tls_cleanup();
exit(1);
#endif /* PR_OBSD_FTP */
}
static int select_read(int rfd)
/* timeout = 20 seconds */
{
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(rfd, &rfds);
tv.tv_sec = 20;
tv.tv_usec = 0;
return select(rfd + 1, &rfds, NULL, NULL, &tv);
}
ssize_t tls_read(int fd, void *buf, size_t count)
{
SSL *s = SOCK_TO_SSL(fd);
retry:
if (s) {
ssize_t c = SSL_read(s, buf, count);
if (c < 0) {
int err = SSL_get_error(s, c);
/* read(2) returns only the generic error number -1 */
c = -1;
switch (err) {
case SSL_ERROR_WANT_READ:
/* OpenSSL needs more data from the wire to finish the current block,
* so we wait a little while for it. */
err = select_read(fd);
if (err > 0)
goto retry;
else if (err == 0)
/* still missing data after timeout, emulate an EINTR and return. */
errno = EINTR;
/* if err < 0, i.e. some error from the select(), everything is already
* in place; errno is properly set and this function returns -1. */
break;
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
/* the SSL connection is screwed up, emulate EOF */
c = 0;
break;
default:
handle_ssl_error(err, "tls_read()");
break;
}
}
return c;
} else
return read(fd, buf, count);
}
int tls_fgetc(FILE *stream)
{
SSL *s = SOCK_TO_SSL(fileno(stream));
if (s) {
unsigned char r;
int err;
do
err = tls_read(fileno(stream), &r, 1);
while (err < 0 && errno == EINTR);
if (err == 1)
return (int) r;
else
return EOF;
} else
return fgetc(stream);
}
int tls_recv(int fd, void *buf, size_t len, int flags)
{
SSL *s = SOCK_TO_SSL(fd);
if (s)
return (int) tls_read(fd, buf, len);
else
return recv(fd, buf, len, flags);
}
ssize_t tls_write(int fd, const void *buf, size_t count)
{
SSL *s = SOCK_TO_SSL(fd);
if (s) {
ssize_t c = SSL_write(s, buf, count);
if (c < 0) {
int err = SSL_get_error(s, c);
/* write(2) returns only the generic error number -1 */
c = -1;
switch (err) {
case SSL_ERROR_WANT_WRITE:
/* simulate an EINTR in case OpenSSL wants to write more */
errno = EINTR;
break;
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
/* the SSL connection is screwed up, emulate EPIPE */
errno = EPIPE;
break;
default:
handle_ssl_error(err, "tls_write()");
break;
}
}
return c;
}
else
return write(fd, buf, count);
}
int tls_fputs(const char *str, FILE *stream)
{
SSL *s = SOCK_TO_SSL(fileno(stream));
if (s) {
int sent = 0, size, w;
size = strlen(str);
do {
w = tls_write(fileno(stream), str + sent, size - sent);
if (w > 0)
sent += w;
else if (!(w < 0 && errno == EINTR))
break; /* other error than EINTR or w == 0 */
} while (sent != size);
if (w < 0)
return EOF;
else
return w;
} else
return fputs(str, stream);
}
void tls_fputc_fflush(int fd)
{
if (fputc_buflen > 0) {
tls_write(fd, fputc_buffer, fputc_buflen);
fputc_buflen = 0;
}
return;
}
int tls_fputc(int c, FILE *stream)
{
SSL *s = SOCK_TO_SSL(fileno(stream));
if (s) {
unsigned char uc = c;
int err = 1;
do {
fputc_buffer[fputc_buflen++] = uc;
if (fputc_buflen >= FPUTC_BUFFERSIZE) {
err = tls_write(fileno(stream), fputc_buffer, fputc_buflen);
if (err >= 0) {
err = 1;
}
fputc_buflen = 0;
}
} while (err < 0 && errno == EINTR);
if (err == 1)
return (int) uc;
else
return EOF;
} else
return fputc(c, stream);
}
int tls_send(int fd, const void *msg, size_t len, int flags)
{
SSL *s = SOCK_TO_SSL(fd);
if (s)
return (int) tls_write(fd, msg, len);
else
return send(fd, msg, len, flags);
}
#ifdef PR_OBSD_FTP
int tls_vfprintf(FILE *stream, const char *format, va_list ap)
{
#define SNP_MAXBUF 1024000
SSL *ssl = SOCK_TO_SSL(fileno(stream));
if (ssl) {
/* here I boldly assume that snprintf() and vsnprintf() uses the same
* return value convention. if not, what kind of libc is this? ;-)
*/
char sbuf[1024] = { 0 }, *buf = sbuf, *lbuf = NULL;
int sent = 0, size, ret, w;
ret = vsnprintf(sbuf, sizeof(sbuf), format, ap);
#ifdef SNPRINTF_OK
/* this one returns the number of bytes it wants to write in case of overflow */
if (ret >= sizeof(sbuf) && ret < SNP_MAXBUF) {
/* sbuf was too small, use a larger lbuf */
lbuf = malloc(ret + 1);
if (lbuf) {
vsnprintf(lbuf, ret + 1, format, ap);
buf = lbuf;
}
}
#else
# ifdef SNPRINTF_HALFBROKEN
/* this one returns the number of bytes written (excl. \0) in case of overflow */
# define SNP_OVERFLOW(x, y) ( x == y ? 1 : 0 )
# define SNP_NOERROR(x) ( x < 0 ? 0 : 1 )
# else
# ifdef SNPRINTF_BROKEN
/* this one returns -1 in case of overflow */
# define SNP_OVERFLOW(x, y) ( x < 0 ? 1 : 0 )
# define SNP_NOERROR(x) ( 1 ) /* if -1 means overflow, what's the error indication? */
# else
# error No valid SNPRINTF_... macro defined!
# endif /* !SNPRINTF_BROKEN */
# endif /* !SNPRINTF_HALFBROKEN */
if (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, sizeof(sbuf) - 1)) {
/* sbuf was too small, use a larger lbuf */
size = sizeof(sbuf);
do {
if ((size *= 2) > SNP_MAXBUF) /* try to double the size */
break;
if (lbuf) free(lbuf);
lbuf = malloc(size);
if (lbuf) {
ret = vsnprintf(lbuf, size, format, ap);
buf = lbuf;
} else
break;
} while (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, size - 1));
}
#endif /* !SNPRINTF_OK */
size = strlen(buf);
do {
w = tls_write(fileno(stream), buf + sent, size - sent);
if (w > 0)
sent += w;
else if (!(w < 0 && errno == EINTR))
break; /* other error than EINTR or w == 0 */
} while (sent != size);
if (lbuf) free(lbuf);
return sent;
} else
return vfprintf(stream, format, ap);
}
#ifdef __STDC__
int tls_fprintf(FILE *stream, const char *fmt, ...)
#else
int tls_fprintf(stream, fmt, va_alist)
FILE *stream;
char *fmt;
va_dcl
#endif
{
va_list ap;
#ifdef __STDC__
va_start(ap, fmt);
#else
va_start(ap);
#endif
return tls_vfprintf(stream, fmt, ap);
}
#endif /* PR_OBSD_FTP */
int tls_fflush(FILE *stream)
{
if (stream == NULL)
return fflush(NULL);
if (SOCK_TO_SSL(fileno(stream)))
return 0; /* don't do anything! */
else
return fflush(stream);
}
static char *glob_tilde(char *s)
/* very simple ~ expansion */
{
char *h, *r;
if (s == NULL)
return NULL;
if (*s != '~')
return s;
if (!(h = getenv("HOME")))
return s;
if (!(r = malloc(strlen(h) + strlen(s))))
return s;
sprintf(r, "%s%s", h, s + 1);
free(s);
return r;
}
int x509rc_read_filenames(void)
{
char filename[MAXPATHLEN], s1[256], s2[MAXPATHLEN], s3[MAXPATHLEN],
line[sizeof(s1) + sizeof(s2) + sizeof(s3) + 50], format[50], *home = NULL,
*p, *host = NULL;
int rv = 0;
FILE *file;
if (x509rc_override) /* cert already specified on command line */
return 0;
if (!tls_hostname)
return 1;
host = strdup(tls_hostname);
if (host == NULL)
return 2;
for (p = host; *p; p++)
*p = tolower(*p);
home = getenv("HOME");
if (home == NULL)
return 3;
snprintf(filename, sizeof(filename), "%s/.x509rc", home);
file = fopen(filename, "r");
if (file == NULL)
return 4;
/* create the sscanf() format string */
snprintf(format, sizeof(format), "%%%us %%%us %%%us", (unsigned int) sizeof(s1) - 1,
(unsigned int) sizeof(s2) - 1, (unsigned int) sizeof(s3) - 1);
while (fgets(line, sizeof(line), file)) {
int c;
if ((p = strchr(line, '#'))) /* truncate at comment */
*p = 0;
c = sscanf(line, format, &s1, &s2, &s3);
if (c < 2)
continue;
for (p = s1; *p; p++)
*p = tolower(*p);
/* check for exact host name match */
if (!strcmp(s1, host)) {
if (tls_cert_file)
free(tls_cert_file);
tls_cert_file = glob_tilde(strdup(s2));
if (c > 2) {
if (tls_key_file)
free(tls_key_file);
tls_key_file = glob_tilde(strdup(s3));
}
goto cleanup;
}
/* check for a "prefix" match */
if (*s1 == '.') {
int hlen = strlen(host);
int slen = strlen(s1);
if (hlen > slen + 1 && !strcmp(s1 + 1, host + hlen - slen + 1)) {
if (tls_cert_file)
free(tls_cert_file);
tls_cert_file = glob_tilde(strdup(s2));
if (c > 2) {
if (tls_key_file)
free(tls_key_file);
tls_key_file = glob_tilde(strdup(s3));
}
}
}
}
cleanup:
fclose(file);
if (host) free(host);
return rv;
}
void tls_ccc( void )
{
int rc;
int shutcount = 0;
/*
* we can only do one CCC, if we do do it, then we need to push the
* current state of the control connection to allow us to clone
* it later for data connections
*/
ctrl_ssl_clone = SSL_new(ssl_ctx);
SSL_copy_session_id(ctrl_ssl_clone, ctrl_conn.ssl);
rc = SSL_shutdown(ctrl_conn.ssl);
/*
* the rc of 0 from SSL_shutdown means - "call it again"
* (this from the doc on www.openssl.org and not the
* man pages :-( )
* Just put a count around it to make sure we don't hang
*/
while((0 == rc) && (shutcount < 5))
{
rc = SSL_shutdown(ctrl_conn.ssl);
shutcount++;
}
if (rc != 1)
{
fprintf(stderr,
"SSL shutdown failed after CCC. Session may be unusable\n");
}
/*
* These just ensure that we don't think that we have SSL on the
* control connection any more
*/
SSL_free(ctrl_conn.ssl);
ctrl_conn.ssl = NULL;
ctrl_conn.sock = -1;
return;
}
syntax highlighted by Code2HTML, v. 0.9.1