racket/collects/openssl/mzssl.c
2005-05-27 18:56:37 +00:00

1838 lines
51 KiB
C

/* ssl.c: an extension to PLT MzScheme to allow SSL connections */
#define OPENSSL_NO_KRB5
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "escheme.h"
#ifdef USE_UNIX_SOCKETS_TCP
# include <sys/types.h>
# include <netinet/in.h>
# include <netdb.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <fcntl.h>
# include <unistd.h>
# include <errno.h>
# define SOCK_ERRNO() errno
# define NOT_WINSOCK(x) (x)
# define INVALID_SOCKET (-1)
# define WAS_EINPROGRESS(e) ((e == EINPROGRESS))
# define mz_h_errno() h_errno
# define mz_hstrerror(x) dup_errstr(hstrerror(x))
#endif
#ifdef USE_WINSOCK_TCP
# include <winsock.h>
# define SOCK_ERRNO() WSAGetLastError()
# define NOT_WINSOCK(x) 0
# define WAS_EINPROGRESS(e) ((e == WSAEWOULDBLOCK))
# define mz_h_errno() WSAGetLastError()
# define mz_hstrerror(x) NULL
#endif
/* stolen from $(PLTHOME}/src/mzscheme/src/schpriv.h */
#ifdef USE_FCNTL_O_NONBLOCK
# define MZ_NONBLOCKING O_NONBLOCK
#else
# define MZ_NONBLOCKING FNDELAY
#endif
/* stolen from $(PLTHOME)/src/mzscheme/src/network.c */
# ifdef PROTOENT_IS_INT
# define PROTO_P_PROTO PROTOENT_IS_INT
# else
# define PROTO_P_PROTO proto->p_proto
# endif
/* stolen from $(PLTHOME)/src/mzscheme/src/schfd.h */
#ifdef USE_FAR_MZ_FDCALLS
# define DECL_FDSET(n, c) static fd_set *n
# define INIT_DECL_FDSET(n, c) (n = (n ? (fd_set *)scheme_init_fdset_array(n,c)\
: (fd_set*)scheme_alloc_fdset_array(c,1)))
#else
# define DECL_FDSET(n, c) fd_set n[c]
# define INIT_DECL_FDSET(n, c) /* empty */
#endif
struct sslplt {
#ifdef MZ_PRECISE_GC
Scheme_Type type;
#endif
SSL *ssl;
char *obuffer; /* Buffer for outgoing bytes, but this is not a "buffer"
at the Scheme level. A daemon thread flushes it.
This is necessary because there's no way to know
whether a write to SSL will succeed, and handing
WANT_READ and WANT_WRITE requires consistent
calls to SSL_write. */
int ob_used; /* Length of data in obuffer. */
char ibuffer; /* One char is enough because SSL_read doesn't appear
to actually need consistency for WANT_READ and WANT_WRITE.
Or maybe I've just been [un]lucky. */
char ib_used; /* 0 or 1, length of data in ibuffer */
char close_in, close_out;
char write_blocked_reason; /* 0 => might not be blocked, 1 => for read, 2 => for write */
struct sslplt *next;
};
#define OBUFFER_SIZE 4096
typedef struct {
Scheme_Object so;
int s;
Scheme_Custodian_Reference *mref;
SSL_CTX *ctx;
} listener_t;
typedef struct {
Scheme_Object so;
SSL_CTX *ctx;
} mzssl_ctx_t;
static Scheme_Type ssl_listener_type;
static Scheme_Type ssl_ctx_type;
#ifdef MZ_PRECISE_GC
static Scheme_Type sslplt_type;
#endif
#define LISTENER_WAS_CLOSED(x) (((listener_t *)(x))->s == INVALID_SOCKET)
#ifndef MZ_PRECISE_GC
# define GC_CAN_IGNORE /* empty */
# define XFORM_OK_PLUS +
#endif
/* create_ register_sslplt: called when a new sslplt structure needs to be
created. */
struct sslplt *create_register_sslplt(SSL *ssl)
{
struct sslplt *sslplt;
char *obuffer;
#ifdef MZ_PRECISE_GC
sslplt = (struct sslplt *)scheme_malloc_tagged(sizeof(struct sslplt));
sslplt->type = sslplt_type;
#else
sslplt = (struct sslplt *)scheme_malloc(sizeof(struct sslplt));
#endif
obuffer = (char *)scheme_malloc_atomic(OBUFFER_SIZE);
sslplt->ssl = ssl;
sslplt->ib_used = 0; sslplt->ob_used = 0;
sslplt->close_in = 0; sslplt->close_out = 0;
sslplt->write_blocked_reason = 0;
sslplt->obuffer = obuffer;
return sslplt;
}
/*****************************************************************************
* GENERC SOCKET CHECKS: ready? and needs-wakeup. *
*****************************************************************************/
int check_socket_ready(int s, int for_write)
{
DECL_FDSET(writefds, 1);
DECL_FDSET(exnfds, 1);
struct timeval time = {0, 0};
int res;
INIT_DECL_FDSET(writefds, 1);
INIT_DECL_FDSET(exnfds, 1);
MZ_FD_ZERO(writefds);
MZ_FD_SET(s, writefds);
MZ_FD_ZERO(exnfds);
MZ_FD_SET(s, exnfds);
do {
res = select(s + 1,
for_write ? NULL : writefds,
for_write ? writefds : NULL,
exnfds, &time);
} while((res == -1) && NOT_WINSOCK(errno == EINTR));
return res;
}
void socket_add_fds(int s, void *fds, int for_write)
{
void *fds1, *fds2;
fds1 = MZ_GET_FDSET(fds, (for_write ? 1 : 0));
fds2 = MZ_GET_FDSET(fds, 2);
MZ_FD_SET(s, (fd_set *)fds1);
MZ_FD_SET(s, (fd_set *)fds2);
}
/*****************************************************************************
* TOP-LEVEL THREAD: This is the routine and data involved with running the *
* top level thread (the one that helps us fake a couple guarantees). *
* When output can't be written without blocking, we put one char in a *
* and promise to flush in a priviledged daemon thread. If this one-char *
* buffer is empty, we can promise a "non-blocking" write to the Scheme *
* level. We need the one-char buffer because the SSL layer offers no way to *
* get a promise that at least one character can be written without *
* blocking. *
*****************************************************************************/
Scheme_Object *daemon_attn = NULL;
struct sslplt *ssls = NULL;
int daemon_ready(Scheme_Object *ignored)
{
struct sslplt *cur;
for (cur = ssls; cur; cur = cur->next) {
if (!cur->write_blocked_reason) {
/* Newly queued, or someone else operated on the same
SSL connection, so we should try again. */
return 1;
} else
/* The SLL layer is waiting for either input or output
on the underlying socket: */
if (check_socket_ready(BIO_get_fd(SSL_get_wbio(cur->ssl), NULL),
(cur->write_blocked_reason == 2)))
return 1;
}
return 0;
}
void deamon_needs_wakeup(Scheme_Object *ignored, void *fds)
{
struct sslplt *cur;
for (cur = ssls; cur; cur = cur->next) {
if (!cur->write_blocked_reason)
scheme_cancel_sleep();
else
socket_add_fds(BIO_get_fd(SSL_get_wbio(cur->ssl), NULL),
fds,
(cur->write_blocked_reason == 2));
}
}
/* write_close_thread: this is the thread that flushes out our buffers
automatically and/or closes items which need closing */
Scheme_Object *write_close_thread(int argc, Scheme_Object *argv[])
{
struct sslplt *cur, *prev;
int empty;
/* this thread should not terminate unless killed externally */
while (1) {
/* Wait until there's something to do: */
scheme_wait_sema(daemon_attn, 0);
while (1) {
cur = ssls; prev = NULL;
while (cur) {
int status, drop = 1;
if (cur->ob_used) {
cur->write_blocked_reason = 0;
/* Try to write: */
status = SSL_write(cur->ssl, cur->obuffer, cur->ob_used);
if (status >= 1) {
cur->ob_used -= status;
if (cur->ob_used) {
memmove(cur->obuffer, cur->obuffer + status, cur->ob_used);
drop = 0;
}
} else {
int err;
drop = 0;
err = SSL_get_error(cur->ssl, status);
if (err == SSL_ERROR_WANT_READ) {
cur->write_blocked_reason = 1;
} else if (err == SSL_ERROR_WANT_WRITE) {
cur->write_blocked_reason = 2;
} else {
/* Some error. We drop the char, and assume
that it's not a transient error, so the
next action will find the same error. */
drop = 1;
cur->ob_used = 0;
}
}
} else if (cur->close_in && cur->close_out) {
/* Apparently a force close */
SSL_free(cur->ssl);
}
/* there shouldn't be a 3rd possibility */
if (!drop) {
prev = cur;
} else{
if (prev)
prev->next = cur->next;
else
ssls = cur->next;
}
cur = cur->next;
}
empty = !ssls;
if (empty)
break;
/* wait until something becomes unblocked, or something new is queued */
scheme_block_until(daemon_ready, deamon_needs_wakeup, NULL, (float)0.0);
}
}
}
/*****************************************************************************
* ERROR FUNCTIONs: wrap the SSL error reporter to get a string and error *
* number that fits into MzScheme's %Z convention. *
* Also, copy error strings provided libraries, in case of a thread swap *
* between the time the string is obtained and the string is put into an *
* error message (otherwise the string could get overwritten?). *
*****************************************************************************/
static const char *dup_errstr(const char *s) {
char *t;
long len;
if (s){
len = strlen(s);
t = scheme_malloc_atomic(len+1);
memcpy(t, s, len+1);
return t;
} else
return s;
}
static int get_ssl_error_msg(int errid, const char **msg, int status, int has_status)
{
if ((errid == SSL_ERROR_SYSCALL) && has_status) {
if (status == 0) {
*msg = "unexpected EOF";
} else {
*msg = NULL;
errid = SOCK_ERRNO();
}
} else {
const char *c;
char buf[121];
if (errid == SSL_ERROR_SSL) {
errid = ERR_get_error();
}
if (errid && (ERR_GET_LIB(errid) == ERR_LIB_SYS)) {
*msg = NULL;
return ERR_GET_REASON(errid);
} else {
memset(buf, 0, 121);
/* wants a buffer of size 120: */
ERR_error_string(errid, buf);
c = dup_errstr(buf);
if (c)
*msg = c;
else
*msg = "Unknown error";
}
}
return errid;
}
/*****************************************************************************
* INPORT PORT FUNCTIONS: This is the stuff that works on input ports. This *
* is a little complicated because we have to get char_ready to work on top *
* of a system that doesn't have such a function. So we buffer one character *
* as necessary. *
*****************************************************************************/
/* this is the new subtype we're creating */
static Scheme_Object *ssl_input_port_type = NULL;
/* forward decls: */
static void sslin_need_wakeup(Scheme_Input_Port *port, void *fds);
static int sslin_char_ready(Scheme_Input_Port *port);
/* ssl_get_string: read a sequence of bytes into a buffer given to us. This is
made severely annoying by the nonblocking nature of the socket stream and
the possibly blocking nature that mzscheme might want from us. */
long ssl_do_get_string(Scheme_Input_Port *port, char *buffer, long offset,
long size, int nonblocking,
int *stuck_why, int err_ok,
Scheme_Object *unless)
{
const char *errstr = "Unknown error";
int err = 0;
long status = 0;
long bytes_read = 0;
struct sslplt *ssl = (struct sslplt *)SCHEME_INPORT_VAL(port);
while (!bytes_read) {
/* check unless before anything else */
if (scheme_unless_ready(unless))
return SCHEME_UNLESS_READY;
/* check the buffer */
if(ssl->ib_used) {
buffer[offset + bytes_read] = ssl->ibuffer;
bytes_read++;
ssl->ib_used = 0;
}
/* make sure people aren't being sneaky */
if(ssl->close_in) {
errstr = "read from closed port!";
goto read_error;
}
/* re-check writes,if any are blocked, since we're touching the
ssl channel */
ssl->write_blocked_reason = 0;
if (ssl->ob_used) {
/* A write needs to be re-tried. Can't read until then. */
bytes_read = 0;
*stuck_why = 3;
} else {
/* read the data. maybe. hopefully. please. */
status = SSL_read(ssl->ssl,
buffer XFORM_OK_PLUS offset XFORM_OK_PLUS bytes_read,
size-bytes_read);
if(status < 1) {
/* see what kind of error this was */
err = SSL_get_error(ssl->ssl, status);
/* see if we've hit the end of file */
if ((err == SSL_ERROR_ZERO_RETURN)
|| ((err == SSL_ERROR_SYSCALL) && !status)) {
if(bytes_read == 0)
return EOF;
else
return bytes_read;
} else if ((err != SSL_ERROR_WANT_READ) && (err != SSL_ERROR_WANT_WRITE)) {
/* critical error */
if (!err_ok) return 0;
err = get_ssl_error_msg(err, &errstr, status, 1);
goto read_error;
}
*stuck_why = ((err == SSL_ERROR_WANT_READ) ? 1 : 2);
} else
bytes_read += status;
}
if (nonblocking > 0)
break;
/* It might be tempting at this point to block on the fd
for reading if SSL_ERROR_WANT_READ. That would be a bad
idea, because another thread might be using the port,
and might shift it into SSL_ERROR_WANT_WRITE mode.
Use the general sll input blocking functions. */
if (!bytes_read) {
while (!sslin_char_ready(port)) {
scheme_block_until_unless((Scheme_Ready_Fun)sslin_char_ready,
(Scheme_Needs_Wakeup_Fun)sslin_need_wakeup,
(void *)port, (float)0.0,
unless,
nonblocking < 0);
scheme_wait_input_allowed(port, nonblocking);
if (scheme_unless_ready(unless))
return SCHEME_UNLESS_READY;
}
}
}
return bytes_read;
read_error:
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-read: error reading (%Z)",
err, errstr);
return 0; /* needless, but it makes GCC happy */
}
long ssl_get_string(Scheme_Input_Port *port, char *buffer, long offset,
long size, int nonblocking,
Scheme_Object *unless)
{
int stuck_why;
return ssl_do_get_string(port, buffer, offset, size, nonblocking,
&stuck_why, 1,
unless);
}
/* sslin_char_ready: return 1 (true) iff a nonblocking call to get_string
can read at least one character (that is, it won't return 0). This
function is the cause of a bit of suffering, actually. */
static int sslin_do_char_ready(Scheme_Input_Port *port, int *stuck_why)
{
struct sslplt *ssl = SCHEME_INPORT_VAL(port);
char buf[1];
int r;
*stuck_why = 0;
if (ssl->close_in) return 1;
/* see if the buffer has something in it, and if so, return true */
if(ssl->ib_used) return 1;
/* otherwise, try to read a character in */
r = ssl_do_get_string(port, buf, 0, 1, 1, stuck_why, 0, NULL);
if (r) {
if (r != EOF) {
ssl->ib_used = 1;
ssl->ibuffer = ((unsigned char *)buf)[0];
}
return 1;
}
if (!*stuck_why) {
/* not-yet-reported error */
return 1;
}
/* nothing buffered and we can't read, so the answer is no */
return 0;
}
static int sslin_char_ready(Scheme_Input_Port *port)
{
int stuck_why;
return sslin_do_char_ready(port, &stuck_why);
}
/* sslin_close: close down a buffer, freeing the temporary structures we
created. */
void sslin_close(Scheme_Input_Port *port)
{
struct sslplt *ssl;
ssl= (struct sslplt *)SCHEME_INPORT_VAL(port);
ssl->close_in = 1;
ssl->write_blocked_reason = 0;
if (ssl->close_out)
SSL_free(ssl->ssl);
}
/* sslin_need_wakeup: called when the input port is blocked to determine
what exactly it's blocked on. We have to try a read to find out
why it's blocked: waiting for input or output on the low-level
socket? */
static void sslin_need_wakeup(Scheme_Input_Port *port, void *fds)
{
struct sslplt *ssl = SCHEME_INPORT_VAL(port);
long rfd;
void *fds2;
int stuck_why;
rfd = BIO_get_fd(SSL_get_rbio(ssl->ssl), NULL);
if (sslin_do_char_ready(port, &stuck_why)) {
/* Need wakeup now! */
scheme_cancel_sleep();
} else {
if (stuck_why != 3)
socket_add_fds(rfd, fds, (stuck_why == 2));
/* but stuck_why == 3 implies that a write is
responsible for waking up */
}
}
/* make_sslin_port: called to create a scheme input port to return to the
caller, eventually */
Scheme_Input_Port *make_sslin_port(SSL *ssl, struct sslplt *wrapper, const char *name)
{
return scheme_make_input_port(ssl_input_port_type, wrapper,
scheme_make_immutable_sized_utf8_string((char *)name, -1),
ssl_get_string,
NULL,
scheme_progress_evt_via_get,
scheme_peeked_read_via_get,
sslin_char_ready, sslin_close,
sslin_need_wakeup, 1);
}
/*****************************************************************************
* OUTPUT PORT FUNCTIONS: This is the stuff that works on output ports. This *
* is very complicated because we have to get char_ready to work on top of a *
* system that doesn't have such a function. So we buffer one character on *
* output as necessary. *
*****************************************************************************/
/* this is the new subtype we're creating */
static Scheme_Object *ssl_output_port_type = NULL;
/* forward decls: */
static int sslout_char_ready(Scheme_Output_Port *port);
static void sslout_need_wakeup(Scheme_Output_Port *port, void *fds);
/* write_string: write some bits of data out to the wire, if possible. This
is made complicated by a host of problems. */
long write_string(Scheme_Output_Port *port, const char *buffer, long offset,
long size, int rarely_block, int enable_break)
{
struct sslplt *ssl = (struct sslplt *)SCHEME_OUTPORT_VAL(port);
const char *errstr = "Unknown error";
int err = 0;
int status = 0;
long out_size;
/* make sure people aren't trying to do something sneaky */
if (ssl->close_out) {
errstr = "write to closed port!";
goto write_error;
}
if (ssl->ob_used) {
if (rarely_block == 2)
return size ? 0 : -1; /* return -1 if this was a flush request */
/* Wait until it's writable */
scheme_block_until_enable_break((Scheme_Ready_Fun)sslout_char_ready,
(Scheme_Needs_Wakeup_Fun)sslout_need_wakeup,
(void *)port, (float)0.0,
enable_break);
}
/* We get here only when !ssl->ob_used. */
if (!size) /* this was a flush request, and we've flushed */
return 0;
/* could have been closed by another thread */
if (ssl->close_out) {
errstr = "write to closed port!";
goto write_error;
}
/* Try to write a decent sized chunk. We have to copy it to
obuffer, in case the write must be continued (in which case
SSL_write insists on getting the same arguments that it received
last time). */
out_size = size;
if (out_size > OBUFFER_SIZE)
out_size = OBUFFER_SIZE;
memcpy(ssl->obuffer, buffer + offset, out_size);
status = SSL_write(ssl->ssl, ssl->obuffer, out_size);
if (status > 0)
return status; /* success */
err = SSL_get_error(ssl->ssl, status);
if((err != SSL_ERROR_WANT_READ) && (err != SSL_ERROR_WANT_WRITE)) {
err = get_ssl_error_msg(err, &errstr, status, 1);
goto write_error;
}
/* Can't write a decent-sized chunk. Put out_size chars in the outgoing
buffer, and block as necessary until those chars are flushed */
if (out_size) {
int was_empty;
ssl->ob_used = out_size;
/* Put this SLL into the list of things that the deamon must
process. */
was_empty = !ssls;
ssl->next = ssls;
ssls = ssl;
/* Wake up the daemon thread if the list used to be empty: */
if (was_empty)
scheme_post_sema(daemon_attn);
/* We "wrote" out_size bytes. The daemon will ensure that the bytes
actually go out. */
return out_size;
} else
return 0;
write_error:
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-write: error writing (%Z)",
err, errstr);
return 0; /* needless, but it makes GCC happy */
}
/* sslout_char_ready: return 1 (true) iff a nonblocking (version 1, not
version 2) call to write_string will write at least one character. */
static int sslout_char_ready(Scheme_Output_Port *port)
{
struct sslplt *ssl = SCHEME_OUTPORT_VAL(port);
return !ssl->ob_used;
}
static int shutdown_ready(Scheme_Object *_ssl)
{
struct sslplt *ssl = (struct sslplt *)_ssl;
if (!ssl->write_blocked_reason)
return 1;
else
return check_socket_ready(BIO_get_fd(SSL_get_wbio(ssl->ssl), NULL),
(ssl->write_blocked_reason == 2));
}
static void shutdown_need_wakeup(Scheme_Object *_ssl, void *fds)
{
struct sslplt *ssl = (struct sslplt *)_ssl;
if (!ssl->write_blocked_reason)
scheme_cancel_sleep();
else
socket_add_fds(BIO_get_fd(SSL_get_wbio(ssl->ssl), NULL),
fds,
(ssl->write_blocked_reason == 2));
}
/* sslout_close: close down a buffer */
void sslout_close(Scheme_Output_Port *port)
{
struct sslplt *ssl = (struct sslplt *)SCHEME_OUTPORT_VAL(port);
int forced = 0;
if (ssl->ob_used && scheme_close_should_force_port_closed()) {
/* Tell daemon to give up on this port,
and don't bother with a shutdown: */
ssl->ob_used = 0;
ssl->write_blocked_reason = 0;
ssl->close_out = 1;
} else {
/* We want to shutdown. If there's still a write in
progress, wait. */
if (ssl->ob_used){
scheme_block_until((Scheme_Ready_Fun)sslout_char_ready,
(Scheme_Needs_Wakeup_Fun)sslout_need_wakeup,
(void *)port, (float)0.0);
}
/* assert: !ssl->ob_used */
/* it's possible that we were shut down in another
thread, though. */
/* FIXME: what if a another thread writes at this point?
In particular, the code below assumes that the deamon
thread is not trying to output. */
while (!ssl->close_out) {
int status;
int err, tries = 0;
while (1) {
status = SSL_shutdown(ssl->ssl);
if (status < 1)
err = SSL_get_error(ssl->ssl, status);
/* Note: SSL_ERROR_SYSCALL may be erroneous if status was 0.
Indeed 0 seems to be the result in many cases because the socket
is non-blocking, and then neither of the WANTs is returned.
We address this by simply trying 10 times and then giving
up. The two-step shutdown is optional, anyway. */
if ((status < 0) && !scheme_close_should_force_port_closed()
/* if an eof occurs, let's agree that it's shut down */
&& !(err == SSL_ERROR_SYSCALL)) {
if (err == SSL_ERROR_WANT_READ)
ssl->write_blocked_reason = 1;
else if (err == SSL_ERROR_WANT_WRITE)
ssl->write_blocked_reason = 2;
else {
const char *errstr;
err = get_ssl_error_msg(err, &errstr, status, 1);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-close: error shutting down output (%Z)",
err, errstr);
return;
}
scheme_block_until(shutdown_ready,
shutdown_need_wakeup,
(void *)ssl, (float)0.0);
} else if (status || (tries > 10)) {
ssl->close_out = 1;
if (ssl->close_in) {
SSL_free(ssl->ssl);
}
break;
} else {
tries++;
scheme_thread_block(0.0);
}
}
}
}
}
/* sslout_need_wakeup: we don't do anything, because low-level
blocking is handled by the daemon thread */
static void sslout_need_wakeup(Scheme_Output_Port *port, void *fds)
{
}
/* make_sslout_port: called to create a scheme output port to return to the
caller, eventually. */
static Scheme_Output_Port *make_sslout_port(SSL *ssl, struct sslplt *data, const char *name)
{
return scheme_make_output_port(ssl_output_port_type, data,
scheme_make_immutable_sized_utf8_string((char *)name, -1),
scheme_write_evt_via_write,
write_string,
sslout_char_ready, sslout_close,
sslout_need_wakeup,
NULL, NULL, 1);
}
/*****************************************************************************
* CLEANING AND NETWORK FUNCTIONS: These are the functions which convert the *
* things we get in to things that are useful, plus the routines for doing *
* various network operations *
*****************************************************************************/
/* check_host_and_convert: Make absolutely sure the first argument was a
string, and then convert it into a character string we can actually use.
Or scream bloody murder if it wasn't a string. */
char *check_host_and_convert(const char *name, int argc, Scheme_Object *argv[], int pos)
{
if (SCHEME_CHAR_STRINGP(argv[pos]))
return SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(argv[pos]));
scheme_wrong_type(name, "string", pos, argc, argv);
return NULL; /* unnecessary, but it makes GCC happy */
}
/* check_port_and_convert: Make absolutely sure the second argument
was a potential port number, and if it is, convert it into a number
we can actually use. Or scream if it wasn't kosher. */
unsigned short check_port_and_convert(const char *name, int argc, Scheme_Object *argv[], int pos)
{
if(SCHEME_INTP(argv[pos]))
if(SCHEME_INT_VAL(argv[pos]) >= 1)
if(SCHEME_INT_VAL(argv[pos]) <= 65535)
return htons(SCHEME_INT_VAL(argv[pos]));
scheme_wrong_type(name, "exact integer in [1, 65535]", pos, argc,argv);
return 0; /* unnessary and wrong, but it makes GCC happy */
}
/* check_encrypt_and_convert: Check the third argument is a valid symbol here,
and convert it to the SSL method function we'll be using if they gave us
a good argument. Otherwise scream. The third argument tells us if we want
client or server method functions. */
SSL_METHOD *check_encrypt_and_convert(const char *name, int argc, Scheme_Object *argv[], int pos, int c, int ctx_ok)
{
Scheme_Object *v;
if(argc <= pos)
return (c ? SSLv23_client_method() : SSLv23_server_method());
v = argv[pos];
if(!SAME_OBJ(v, scheme_intern_symbol("sslv2-or-v3"))) {
return (c ? SSLv23_client_method() : SSLv23_server_method());
} else if(!SAME_OBJ(v, scheme_intern_symbol("sslv2"))) {
return (c ? SSLv2_client_method() : SSLv2_server_method());
} else if(!SAME_OBJ(v, scheme_intern_symbol("sslv3"))) {
return (c ? SSLv3_client_method() : SSLv3_server_method());
} else if(!SAME_OBJ(v, scheme_intern_symbol("tls"))) {
return (c ? TLSv1_client_method() : TLSv1_server_method());
} else {
# define ALLOWED_SYMS "'sslv2-or-v3, 'sslv2, 'sslv3, or 'tls"
scheme_wrong_type(name,
ctx_ok ? "ssl-client-context, " ALLOWED_SYMS : ALLOWED_SYMS,
pos, argc, argv);
return NULL; /* unnecessary, but it makes GCC happy */
}
}
/* ssl_check_sock: determine if a socket is ready for reading or
writing; conector_p is an array of integers: socket and
0=>read/1=>write. */
int ssl_check_sock(Scheme_Object *connector_p)
{
return check_socket_ready(((int *)connector_p)[0], ((int *)connector_p)[1]);
}
/* ssl_check_sock: block on socket for reading or
writing; conector_p is an array of integers: socket and
0=>read/1=>write. */
void ssl_sock_needs_wakeup(Scheme_Object *connector_p, void *fds)
{
socket_add_fds(((int *)connector_p)[0], fds, ((int *)connector_p)[1]);
}
#ifdef USE_UNIX_SOCKETS_TCP
/* closesocket: close a socket, and try real hard to do it. This is lifted
entirely from ${PLTHOME}/src/mzscheme/src/network.c */
void closesocket(long s)
{
int cr;
do { cr = close(s); } while((cr == -1) && NOT_WINSOCK(errno == EINTR));
}
#endif
/* close_socket_and_dec: called when we're broken out of our attempt to
connect a socket */
void close_socket_and_dec(unsigned short sock)
{
closesocket(sock);
}
/*****************************************************************************
* SOCKET->SSL connection completion *
*****************************************************************************/
static Scheme_Object *finish_ssl(const char *name, int sock, SSL_METHOD *meth,
char *address, int port, int do_accept,
SSL_CTX *ctx_in)
{
SSL_CTX *ctx = NULL;
BIO *bio = NULL;
SSL *ssl = NULL;
struct sslplt *sslplt = NULL;
const char *errstr = "Unknown error";
Scheme_Object *retval[2];
int status;
int err = 0;
int *sptr = NULL;
/* set up the BIO pipe */
bio = BIO_new_socket(sock, BIO_CLOSE);
if(!bio) { errstr = "couldn't create BIO stream"; goto clean_up_and_die; }
/* set up the SSL context object */
if (!ctx_in) {
ctx = SSL_CTX_new(meth);
if(!ctx) {
err = get_ssl_error_msg(ERR_get_error(), &errstr, 0, 0);
goto clean_up_and_die;
}
}
/* set up the full SSL object */
ssl = SSL_new(ctx ? ctx : ctx_in);
if(!ssl) {
err = get_ssl_error_msg(ERR_get_error(), &errstr, 0, 0);
goto clean_up_and_die;
}
SSL_set_bio(ssl, bio, bio);
if (ctx) {
SSL_CTX_free(ctx); /* ssl has incremented ref count */
ctx = NULL;
}
/* see if we can connect via SSL */
if (do_accept)
status = SSL_accept(ssl);
else
status = SSL_connect(ssl);
while(status < 1) {
err = SSL_get_error(ssl, status);
if ((err == SSL_ERROR_WANT_READ)
|| (err == SSL_ERROR_WANT_WRITE)) {
if (!sptr) {
sptr = (int *)scheme_malloc_atomic(2 * sizeof(int));
sptr[0] = sock;
}
sptr[1] = (err == SSL_ERROR_WANT_WRITE);
BEGIN_ESCAPEABLE(close_socket_and_dec, sock);
scheme_block_until(ssl_check_sock, ssl_sock_needs_wakeup,
(void *)sptr, (float)0.0);
END_ESCAPEABLE();
} else {
err = get_ssl_error_msg(err, &errstr, status, 1);
goto clean_up_and_die;
}
if (do_accept)
status = SSL_accept(ssl);
else
status = SSL_connect(ssl);
}
sslplt = create_register_sslplt(ssl);
retval[0] = (Scheme_Object*)make_sslin_port(ssl, sslplt, address);
retval[1] = (Scheme_Object*)make_sslout_port(ssl, sslplt, address);
return scheme_values(2, retval);
clean_up_and_die:
if (ctx)
SSL_CTX_free(ctx);
if (ssl)
SSL_free(ssl);
else {
if (bio)
BIO_free(bio);
else {
if(sock)
closesocket(sock);
}
}
if (do_accept)
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"%s: accepted connection failed (%Z)",
name,
err, errstr);
else
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"%s: connection to %s, port %d failed (%Z)",
name,
address, port, err, errstr);
/* not strictly necessary, but it makes our C compiler happy */
return NULL;
}
#ifdef USE_WINSOCK_TCP
static int started;
static void TCP_INIT(char *name)
{
static int started = 0;
if (!started) {
WSADATA data;
if (!WSAStartup(MAKEWORD(1, 1), &data)) {
started = 1;
return;
}
} else
return;
scheme_raise_exn(MZEXN_FAIL_UNSUPPORTED,
"%s: not supported on this machine"
" (no winsock driver)",
name);
}
#else
# define TCP_INIT(n) /* empty */
#endif
/*****************************************************************************
* SCHEME EXTERNAL FUNCTION IMPLEMENTATIONS: These are the implemenations of *
* the functions which are actually going to be exported to MzScheme userland*
*****************************************************************************/
static Scheme_Object *ssl_connect(int argc, Scheme_Object *argv[])
{
char *address;
unsigned short nport;
int port;
SSL_METHOD *meth;
SSL_CTX *ctx;
int status;
const char *errstr = "Unknown error";
int err = 0;
GC_CAN_IGNORE struct sockaddr_in addr;
int sock;
#ifndef PROTOENT_IS_INT
struct protoent *proto;
#endif
address = check_host_and_convert("ssl-connect", argc, argv, 0);
nport = check_port_and_convert("ssl-connect", argc, argv, 1);
port = SCHEME_INT_VAL(argv[1]);
if ((argc > 2) && SAME_TYPE(SCHEME_TYPE(argv[2]), ssl_ctx_type)) {
meth = NULL;
ctx = ((mzssl_ctx_t *)(argv[2]))->ctx;
} else {
meth = check_encrypt_and_convert("ssl-connect", argc, argv, 2, 1, 1);
ctx = NULL;
}
/* check we have the security clearance to actually do this */
scheme_security_check_network("ssl-connect", address, port, 1);
scheme_custodian_check_available(NULL, "ssl-connect", "network");
TCP_INIT("ssl-connect");
/* try to create the socket */
#ifndef PROTOENT_IS_INT
proto = getprotobyname("tcp");
if(!proto) {
errstr = "couldn't find tcp protocol id"; goto clean_up_and_die;
}
#endif
sock = socket(PF_INET, SOCK_STREAM, PROTO_P_PROTO);
if (sock == INVALID_SOCKET) { errstr = NULL; err = SOCK_ERRNO(); goto clean_up_and_die; }
#ifdef USE_WINSOCK_TCP
{
unsigned long ioarg = 1;
ioctlsocket(sock, FIONBIO, &ioarg);
}
#else
fcntl(sock, F_SETFL, MZ_NONBLOCKING);
#endif
/* lookup hostname and get a reasonable structure */
if (!scheme_get_host_address(address, nport, &addr)) {
err = 0;
errstr = "Unknown error resolving address";
goto clean_up_and_die;
}
status = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
/* here's the complicated bit */
if (status) {
int errid;
errid = SOCK_ERRNO();
if (!WAS_EINPROGRESS(errid)) {
errstr = NULL; err = errid; goto clean_up_and_die;
}
{
int *sptr;
sptr = (int *)scheme_malloc_atomic(2 * sizeof(int));
sptr[0] = sock;
sptr[1] = 1;
BEGIN_ESCAPEABLE(close_socket_and_dec, sock);
scheme_block_until(ssl_check_sock, ssl_sock_needs_wakeup,
(void *)sptr, (float)0.0);
END_ESCAPEABLE();
}
/* see if the connection succeeded, or die if it didn't */
{
int so_len = sizeof(status);
if(getsockopt(sock, SOL_SOCKET,SO_ERROR, (void*)&status, &so_len) != 0) {
errstr = NULL;
err = status;
goto clean_up_and_die;
}
}
}
return finish_ssl("ssl-connect", sock, meth, address, port, 0, ctx);
clean_up_and_die:
if (sock != INVALID_SOCKET) closesocket(sock);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-connect: connection to %T, port %d failed (%Z)",
argv[0], SCHEME_INT_VAL(argv[1]),
err, errstr);
/* not strictly necessary, but it makes our C compiler happy */
return NULL;
}
static Scheme_Object *ssl_connect_break(int argc, Scheme_Object *argv[]) {
return scheme_call_enable_break(ssl_connect, argc, argv);
}
/*****************************************************************************
* SSL LISTENER: sadly, cut-and-paste from the MzScheme source. *
*****************************************************************************/
/* Forward declaration */
static int stop_listener(Scheme_Object *o);
static Scheme_Object *
ssl_listen(int argc, Scheme_Object *argv[])
{
unsigned short id, origid;
int backlog, errid;
int reuse = 0;
const char *address = NULL;
# ifndef PROTOENT_IS_INT
struct protoent *proto;
# endif
SSL_METHOD *meth;
SSL_CTX *ctx;
id = check_port_and_convert("ssl-listen", argc, argv, 0);
if (argc > 1) {
if (!SCHEME_INTP(argv[1]) || (SCHEME_INT_VAL(argv[1]) < 1))
scheme_wrong_type("ssl-listen", "small positive integer", 1, argc, argv);
}
if (argc > 2)
reuse = SCHEME_TRUEP(argv[2]);
if (argc > 3) {
if (!SCHEME_FALSEP(argv[3]))
address = check_host_and_convert("ssl-listen", argc, argv, 3);
}
if (0 && (argc > 4) && SAME_TYPE(SCHEME_TYPE(argv[4]), ssl_ctx_type)) {
meth = NULL;
ctx = ((mzssl_ctx_t *)(argv[4]))->ctx;
} else {
meth = check_encrypt_and_convert("ssl-connect", argc, argv, 4, 0, 0);
ctx = NULL;
}
TCP_INIT("ssl-listen");
origid = (unsigned short)SCHEME_INT_VAL(argv[0]);
if (argc > 1)
backlog = SCHEME_INT_VAL(argv[1]);
else
backlog = 4;
scheme_security_check_network("ssl-listen", address, origid, 0);
scheme_custodian_check_available(NULL, "ssl-listen", "network");
if (!ctx) {
ctx = SSL_CTX_new(meth);
if(!ctx) {
const char *errstr;
errid = get_ssl_error_msg(ERR_get_error(), &errstr, 0, 0);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"sll-listen: context creation failed for listen on %d (%Z)",
origid, errid, errstr);
return scheme_void;
}
}
# ifndef PROTOENT_IS_INT
proto = getprotobyname("tcp");
if (proto)
# endif
{
GC_CAN_IGNORE struct sockaddr_in tcp_listen_addr;
if (scheme_get_host_address(address, id, &tcp_listen_addr)) {
int s;
s = socket(PF_INET, SOCK_STREAM, PROTO_P_PROTO);
if (s != INVALID_SOCKET) {
#ifdef USE_WINSOCK_TCP
unsigned long ioarg = 1;
ioctlsocket(s, FIONBIO, &ioarg);
#else
fcntl(s, F_SETFL, MZ_NONBLOCKING);
#endif
if (reuse) {
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
}
if (!bind(s, (struct sockaddr *)&tcp_listen_addr, sizeof(tcp_listen_addr))) {
if (!listen(s, backlog)) {
listener_t *l;
l = (listener_t *)scheme_malloc_tagged(sizeof(listener_t));
l->so.type = ssl_listener_type;
l->s = s;
l->ctx = ctx;
{
Scheme_Custodian_Reference *mref;
mref = scheme_add_managed(NULL,
(Scheme_Object *)l,
(Scheme_Close_Custodian_Client *)stop_listener,
NULL,
1);
l->mref = mref;
}
return (Scheme_Object *)l;
}
}
errid = SOCK_ERRNO();
closesocket(s);
} else
errid = SOCK_ERRNO();
} else {
if (ctx && meth)
SSL_CTX_free(ctx);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-listen: host not found: %s",
address);
return NULL;
}
}
# ifndef PROTOENT_IS_INT
else {
errid = SOCK_ERRNO();
}
# endif
if (ctx && meth)
SSL_CTX_free(ctx);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"sll-listen: listen on %d failed (%E)",
origid, errid);
return NULL;
}
static int stop_listener(Scheme_Object *o)
{
int was_closed = 0;
{
int s = ((listener_t *)o)->s;
if (s == INVALID_SOCKET)
was_closed = 1;
else {
closesocket(s);
((listener_t *)o)->s = INVALID_SOCKET;
scheme_remove_managed(((listener_t *)o)->mref, o);
SSL_CTX_free(((listener_t *)o)->ctx);
}
}
return was_closed;
}
static Scheme_Object *
ssl_close(int argc, Scheme_Object *argv[])
{
int was_closed;
if (!SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type))
scheme_wrong_type("ssl-close", "ssl-listener", 0, argc, argv);
was_closed = stop_listener(argv[0]);
if (was_closed) {
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-close: listener was already closed");
return NULL;
}
return scheme_void;
}
static Scheme_Object *
ssl_listener_p(int argc, Scheme_Object *argv[])
{
return ((SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type))
? scheme_true
: scheme_false);
}
enum {
mzssl_CERT_CHAIN,
mzssl_CERT_ROOT,
mzssl_CERT_REQ,
mzssl_RSA_KEY
};
static Scheme_Object *
ctx_load_file(const char *name, int mode, int client_ok, int argc, Scheme_Object *argv[])
{
char *filename;
const char *what;
int result, use_rsa = 1, format = SSL_FILETYPE_PEM;
SSL_CTX *ctx;
if (!SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type)
&& (! client_ok || !SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_ctx_type)))
scheme_wrong_type(name,
(client_ok ? "ssl-listener or ssl-client-context" : "ssl-listener"),
0, argc, argv);
if (!SCHEME_PATHP(argv[1]))
scheme_wrong_type(name, "string", 1, argc, argv);
if (mode == mzssl_RSA_KEY) {
if (argc > 2)
use_rsa = SCHEME_TRUEP(argv[2]);
if (argc > 3)
if (SCHEME_TRUEP(argv[3]))
format = SSL_FILETYPE_ASN1;
}
filename = scheme_expand_string_filename(argv[1],
name,
NULL,
SCHEME_GUARD_FILE_READ);
if (SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type))
ctx = ((listener_t *)(argv[0]))->ctx;
else
ctx = ((mzssl_ctx_t *)(argv[0]))->ctx;
if (mode == mzssl_CERT_CHAIN) {
result = SSL_CTX_use_certificate_chain_file(ctx, filename);
what = "certificate chain";
} else if (mode == mzssl_CERT_ROOT) {
result = SSL_CTX_load_verify_locations(ctx, filename, NULL);
what = "root certificates";
} else if (mode == mzssl_CERT_REQ) {
GC_CAN_IGNORE STACK_OF(X509_NAME) *stk;
stk = SSL_load_client_CA_file(filename);
if (stk) {
result = 1;
SSL_CTX_set_client_CA_list(ctx, stk);
} else
result = 0;
what = "suggested certificate authorities";
} else if (mode == mzssl_RSA_KEY) {
if (use_rsa)
result = SSL_CTX_use_RSAPrivateKey_file(ctx, filename, format);
else
result = SSL_CTX_use_PrivateKey_file(ctx, filename, format);
what = "private key";
}
if (result != 1) {
int errid;
const char *errstr;
errid = get_ssl_error_msg(ERR_get_error(), &errstr, 0, 0);
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"%s: %s load failed from: %s (%Z)",
name, what, filename, errid, errstr);
return NULL;
}
return scheme_void;
}
static Scheme_Object *
ssl_load_cert_chain(int argc, Scheme_Object *argv[])
{
return ctx_load_file("ssl-load-certificate-chain!", mzssl_CERT_CHAIN, 1, argc, argv);
}
static Scheme_Object *
ssl_load_cert_root(int argc, Scheme_Object *argv[])
{
return ctx_load_file("ssl-load-root-verify-certificate!", mzssl_CERT_ROOT, 1, argc, argv);
}
static Scheme_Object *
ssl_load_accept_cert_auth(int argc, Scheme_Object *argv[])
{
return ctx_load_file("ssl-load-suggested-certificate-authorities!", mzssl_CERT_REQ, 0, argc, argv);
}
static Scheme_Object *
ssl_load_priv_key(int argc, Scheme_Object *argv[])
{
return ctx_load_file("ssl-load-prvate-key!", mzssl_RSA_KEY, 1, argc, argv);
}
static Scheme_Object *
ssl_set_verify(int argc, Scheme_Object *argv[])
{
SSL_CTX *ctx;
if (!SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type)
&& !SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_ctx_type))
scheme_wrong_type("ssl-set-verify!", "ssl-listener or ssl-client-context", 0, argc, argv);
if (SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type))
ctx = ((listener_t *)(argv[0]))->ctx;
else
ctx = ((mzssl_ctx_t *)(argv[0]))->ctx;
SSL_CTX_set_verify(ctx,
(SCHEME_TRUEP(argv[1])
? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
: SSL_VERIFY_NONE),
NULL);
return scheme_void;
}
static void release_ctx(void *p, void *data)
{
SSL_CTX_free(((mzssl_ctx_t *)p)->ctx);
}
static Scheme_Object *
ssl_mk_ctx(int argc, Scheme_Object *argv[])
{
mzssl_ctx_t *c;
SSL_METHOD *meth;
SSL_CTX *ctx;
meth = check_encrypt_and_convert("ssl-make-context", argc, argv, 0, 1, 0);
c = (mzssl_ctx_t *)scheme_malloc_tagged(sizeof(mzssl_ctx_t));
c->so.type = ssl_ctx_type;
ctx = SSL_CTX_new(meth);
if (!ctx) {
const char *errstr;
int errid;
errid = get_ssl_error_msg(ERR_get_error(), &errstr, 0, 0);
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"sll-make-context: context creation failed (%Z)",
errid, errstr);
return scheme_void;
}
c->ctx = ctx;
scheme_add_finalizer(c, release_ctx, NULL);
return (Scheme_Object *)c;
}
static Scheme_Object *
ssl_ctx_p(int argc, Scheme_Object *argv[])
{
return ((SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_ctx_type))
? scheme_true
: scheme_false);
}
static int tcp_check_accept(Scheme_Object *listener)
{
if (LISTENER_WAS_CLOSED(listener))
return 1;
return check_socket_ready(((listener_t *)listener)->s, 0);
}
static void tcp_accept_needs_wakeup(Scheme_Object *listener, void *fds)
{
socket_add_fds(((listener_t *)listener)->s, fds, 0);
}
static Scheme_Object *
ssl_accept(int argc, Scheme_Object *argv[])
{
int was_closed = 0, errid;
Scheme_Object *listener;
int s;
int l;
GC_CAN_IGNORE struct sockaddr_in tcp_accept_addr;
if (!SAME_TYPE(SCHEME_TYPE(argv[0]), ssl_listener_type))
scheme_wrong_type("ssl-accept", "ssl-listener", 0, argc, argv);
scheme_custodian_check_available(NULL, "ssl-accept", "network");
listener = argv[0];
was_closed = LISTENER_WAS_CLOSED(listener);
if (!was_closed) {
scheme_block_until(tcp_check_accept, tcp_accept_needs_wakeup,
listener, (float)0.0);
was_closed = LISTENER_WAS_CLOSED(listener);
}
if (was_closed) {
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-accept: listener is closed");
return NULL;
}
scheme_custodian_check_available(NULL, "ssl-accept", "network");
s = ((listener_t *)listener)->s;
l = sizeof(tcp_accept_addr);
do {
s = accept(s, (struct sockaddr *)&tcp_accept_addr, &l);
} while ((s == -1) && (NOT_WINSOCK(errno) == EINTR));
if (s != INVALID_SOCKET) {
# ifdef USE_WINSOCK_TCP
{
unsigned long ioarg = 1;
ioctlsocket(s, FIONBIO, &ioarg);
}
# else
fcntl(s, F_SETFL, MZ_NONBLOCKING);
# endif
return finish_ssl("ssl-accept", s, NULL, NULL, 0,
1, ((listener_t *)listener)->ctx);
}
errid = SOCK_ERRNO();
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-accept: accept from listener failed (%E)", errid);
return NULL;
}
static Scheme_Object *ssl_accept_break(int argc, Scheme_Object *argv[]) {
return scheme_call_enable_break(ssl_accept, argc, argv);
}
/*****************************************************************************
* MISC *
*****************************************************************************/
static Scheme_Object *ssl_addresses(int argc, Scheme_Object *argv[])
{
/* Again, sadly cut-and-paste from MzScheme's network.c */
GC_CAN_IGNORE struct sockaddr_in tcp_here_addr, tcp_there_addr;
int l, closed;
struct sslplt *wrapper = NULL;
unsigned long here_a, there_a;
unsigned char *b;
Scheme_Object *result[2];
char sa[20];
int fd;
if (SCHEME_OUTPORTP(argv[0])) {
Scheme_Output_Port *op;
op = (Scheme_Output_Port *)argv[0];
if (op->sub_type == ssl_output_port_type) {
wrapper = (struct sslplt *)op->port_data;
fd = BIO_get_fd(SSL_get_wbio(wrapper->ssl), NULL);
}
closed = op->closed;
} else if (SCHEME_INPORTP(argv[0])) {
Scheme_Input_Port *ip;
ip = (Scheme_Input_Port *)argv[0];
if (ip->sub_type == ssl_input_port_type) {
wrapper = (struct sslplt *)ip->port_data;
fd = BIO_get_fd(SSL_get_rbio(wrapper->ssl), NULL);
}
closed = ip->closed;
}
if (!wrapper)
scheme_wrong_type("ssl-addresses", "ssl-port", 0, argc, argv);
if (closed)
scheme_raise_exn(MZEXN_FAIL,
"ssl-addresses: port is closed");
l = sizeof(tcp_here_addr);
if (getsockname(fd, (struct sockaddr *)&tcp_here_addr, &l)) {
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-addresses: could not get local address (%e)",
SOCK_ERRNO());
}
l = sizeof(tcp_there_addr);
if (getpeername(fd, (struct sockaddr *)&tcp_there_addr, &l)) {
scheme_raise_exn(MZEXN_FAIL_NETWORK,
"ssl-addresses: could not get peer address (%e)",
SOCK_ERRNO());
}
here_a = *(unsigned long *)&tcp_here_addr.sin_addr;
there_a = *(unsigned long *)&tcp_there_addr.sin_addr;
b = (unsigned char *)&here_a;
sprintf(sa, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
result[0] = scheme_make_utf8_string(sa);
b = (unsigned char *)&there_a;
sprintf(sa, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
result[1] = scheme_make_utf8_string(sa);
return scheme_values(2, result);
}
/*****************************************************************************
* PRECISE GC *
*****************************************************************************/
#ifdef MZ_PRECISE_GC
int sslplt_SIZE(void *p) {
return
gcBYTES_TO_WORDS(sizeof(struct sslplt));
}
int sslplt_MARK(void *p) {
struct sslplt *ssl = (struct sslplt *)p;
gcMARK(ssl->next);
gcMARK(ssl->obuffer);
return
gcBYTES_TO_WORDS(sizeof(struct sslplt));
}
int sslplt_FIXUP(void *p) {
struct sslplt *ssl = (struct sslplt *)p;
gcFIXUP(ssl->next);
gcFIXUP(ssl->obuffer);
return
gcBYTES_TO_WORDS(sizeof(struct sslplt));
}
int listener_SIZE(void *p) {
return
gcBYTES_TO_WORDS(sizeof(listener_t));
}
int listener_MARK(void *p) {
listener_t *l = (listener_t *)p;
gcMARK(l->mref);
return
gcBYTES_TO_WORDS(sizeof(listener_t));
}
int listener_FIXUP(void *p) {
listener_t *l = (listener_t *)p;
gcFIXUP(l->mref);
return
gcBYTES_TO_WORDS(sizeof(listener_t));
}
int mzssl_ctx_SIZE(void *p) {
return
gcBYTES_TO_WORDS(sizeof(mzssl_ctx_t));
}
int mzssl_ctx_MARK(void *p) {
mzssl_ctx_t *l = (mzssl_ctx_t *)p;
return
gcBYTES_TO_WORDS(sizeof(mzssl_ctx_t));
}
int mzssl_ctx_FIXUP(void *p) {
mzssl_ctx_t *l = (mzssl_ctx_t *)p;
return
gcBYTES_TO_WORDS(sizeof(mzssl_ctx_t));
}
#endif
/*****************************************************************************
* REGISTRATION FUNCTIONS: The functions that register the above externals so*
* everybody else can use them. *
*****************************************************************************/
/* scheme_initialize: called when the extension is first loaded */
Scheme_Object *scheme_initialize(Scheme_Env *env)
{
Scheme_Object *thread;
Scheme_Custodian *newcust;
Scheme_Config *cfg;
thread = scheme_make_prim_w_arity(write_close_thread,
"SSL Flushing Thread",
0, 0);
newcust = scheme_make_custodian(NULL);
scheme_register_extension_global(&daemon_attn, sizeof(daemon_attn));
scheme_register_extension_global(&ssls, sizeof(ssls));
scheme_register_extension_global(&ssl_input_port_type, sizeof(ssl_input_port_type));
scheme_register_extension_global(&ssl_output_port_type, sizeof(ssl_output_port_type));
SSL_library_init();
daemon_attn = scheme_make_sema(0);
ssl_listener_type = scheme_make_type("<ssl-listener>");
ssl_ctx_type = scheme_make_type("<ssl-client-context>");
#ifdef MZTAG_REQUIRED
sslplt_type = scheme_make_type("<ssl-plt-internal>");
#endif
ssl_input_port_type = scheme_make_port_type("<ssl-input-port>");
ssl_output_port_type = scheme_make_port_type("<ssl-output-port>");
#ifdef MZ_PRECISE_GC
GC_register_traversers(ssl_listener_type, listener_SIZE,
listener_MARK, listener_FIXUP,
1, 0);
GC_register_traversers(ssl_ctx_type, mzssl_ctx_SIZE,
mzssl_ctx_MARK, mzssl_ctx_FIXUP,
1, 1);
GC_register_traversers(sslplt_type, sslplt_SIZE,
sslplt_MARK, sslplt_FIXUP,
1, 0);
#endif
SSL_load_error_strings();
scheme_add_evt(ssl_listener_type,
tcp_check_accept, tcp_accept_needs_wakeup,
NULL, 0);
scheme_thread_w_details(thread, NULL, NULL, NULL, newcust, 0);
return scheme_reload(env);
}
/* scheme_reload: called when an extension is loaded a second+ time */
Scheme_Object *scheme_reload(Scheme_Env *env)
{
Scheme_Object *v;
v = scheme_intern_symbol("mzssl");
env = scheme_primitive_module(v, env);
/* add ssl-connect */
v = scheme_make_prim_w_arity(ssl_connect, "ssl-connect", 2, 3);
scheme_add_global("ssl-connect", v, env);
v = scheme_make_prim_w_arity(ssl_connect_break,"ssl-connect/enable-break",2,3);
scheme_add_global("ssl-connect/enable-break", v, env);
v = scheme_make_prim_w_arity(ssl_listen,"ssl-listen",1,5);
scheme_add_global("ssl-listen", v, env);
v = scheme_make_prim_w_arity(ssl_close,"ssl-close",1,1);
scheme_add_global("ssl-close", v, env);
v = scheme_make_prim_w_arity(ssl_listener_p,"ssl-listener?",1,1);
scheme_add_global("ssl-listener?", v, env);
v = scheme_make_prim_w_arity(ssl_load_cert_chain,"ssl-load-certificate-chain!",2,2);
scheme_add_global("ssl-load-certificate-chain!", v, env);
v = scheme_make_prim_w_arity(ssl_load_cert_root,"ssl-load-verify-root-certificates!",2,2);
scheme_add_global("ssl-load-verify-root-certificates!", v, env);
v = scheme_make_prim_w_arity(ssl_load_cert_chain,"ssl-load-suggested-certificate-authorities!",2,2);
scheme_add_global("ssl-load-suggested-certificate-authorities!", v, env);
v = scheme_make_prim_w_arity(ssl_load_priv_key,"ssl-load-private-key!",2,4);
scheme_add_global("ssl-load-private-key!", v, env);
v = scheme_make_prim_w_arity(ssl_set_verify,"ssl-set-verify!",2,2);
scheme_add_global("ssl-set-verify!", v, env);
v = scheme_make_prim_w_arity(ssl_mk_ctx,"ssl-make-client-context",0,0);
scheme_add_global("ssl-make-client-context", v, env);
v = scheme_make_prim_w_arity(ssl_ctx_p,"ssl-client-context?",1,1);
scheme_add_global("ssl-client-context?", v, env);
v = scheme_make_prim_w_arity(ssl_accept,"ssl-accept",1,1);
scheme_add_global("ssl-accept", v, env);
v = scheme_make_prim_w_arity(ssl_accept_break,"ssl-accept/enable-break",1,1);
scheme_add_global("ssl-accept/enable-break", v, env);
v = scheme_make_prim_w_arity(ssl_mk_ctx,"ssl-make-context",0,1);
scheme_add_global("ssl-make-context", v, env);
v = scheme_make_prim_w_everything(ssl_addresses, 0, "ssl-addresses", 1, 1, 0, 2, 2);
scheme_add_global("ssl-addresses", v, env);
scheme_add_global("ssl-available?", scheme_true, env);
scheme_finish_primitive_module(env);
return scheme_void;
}
/* scheme_module_name: called to get the name of this module */
Scheme_Object *scheme_module_name()
{
return scheme_intern_symbol("mzssl");
}