netscape-revival
security/cmd/ssld/ssld.c
/*
** Copyright (c) 1995. Netscape Communications Corporation. All rights
** reserved. This use of this Secure Sockets Layer Reference
** Implementation (the "Software") is governed by the terms of the SSL
** Reference Implementation License Agreement. Please read the
** accompanying "License" file for a description of the rights granted.
** Any other third party materials you use with this Software may be
** subject to additional license restrictions from the licensors of such
** third party software and/or additional export restrictions. The SSL
** Implementation License Agreement grants you no rights to any such
** third party material.
*/
#include <sys/wait.h>
#if !defined(LINUX)
#include <sys/filio.h>
#endif
#include <sys/resource.h>
#include <netinet/tcp.h>
#include "ssld.h"
#include "secutil.h"
extern int getopt(int, char *const*, const char*);
extern char *optarg;
extern int optind;
extern int rresvport( int *port );
static char *defaultKeyDir = "/usr/etc/ssl";
static char *defaultConfFile = "ssld.conf";
static SECCertDBHandle *cert_db_handle;
char *progName;
int dbg, interactive = 1;
static int nfd;
static int max_files = 32;
static fd_set work;
#ifdef __alpha
void DebugPrint(va_alist)
va_dcl
{
va_list ap;
char *fmt;
if (dbg) {
va_start(ap);
fmt = va_arg(ap, char *);
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, fmt, ap);
if (strrchr(fmt, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
char *buf = malloc(50000);
vsprintf(buf, fmt, ap);
syslog(LOG_DEBUG, buf);
free(buf);
}
va_end(ap);
}
}
void Notice(va_alist)
va_dcl
{
va_list ap;
char *fmt;
va_start(ap);
fmt = va_arg(ap, char *);
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, fmt, ap);
if (strrchr(fmt, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
char *buf = malloc(50000);
vsprintf(buf, fmt, ap);
syslog(LOG_DEBUG, buf);
free(buf);
}
va_end(ap);
}
void Error(char *va_alist, ...)
{
va_list ap;
char *fmt;
va_start(ap);
fmt = va_arg(ap, char *);
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, fmt, ap);
if (strrchr(fmt, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
char *buf = malloc(50000);
vsprintf(buf, fmt, ap);
syslog(LOG_DEBUG, buf);
free(buf);
}
va_end(ap);
}
void Fatal(char *va_alist, ...)
{
va_list ap;
char *fmt;
va_start(ap);
fmt = va_arg(ap, char *);
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, fmt, ap);
if (strrchr(fmt, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
char *buf = malloc(50000);
vsprintf(buf, fmt, ap);
syslog(LOG_DEBUG, buf);
free(buf);
}
va_end(ap);
exit(-1);
}
#else
void DebugPrint(char *va_alist, ...)
{
va_list ap;
if (dbg) {
#ifdef va_dcl
va_start(ap);
#else
va_start(ap, va_alist);
#endif
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, va_alist, ap);
if (strrchr(va_alist, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
#ifdef va_dcl
char *buf = malloc(50000);
vsprintf(buf, va_alist, ap);
syslog(LOG_DEBUG, buf);
free(buf);
#else
vsyslog(LOG_DEBUG, va_alist, ap);
#endif
}
va_end(ap);
}
}
void Notice(char *va_alist, ...)
{
va_list ap;
#ifdef va_dcl
va_start(ap);
#else
va_start(ap, va_alist);
#endif
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, va_alist, ap);
if (strrchr(va_alist, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
#ifdef va_dcl
char *buf = malloc(50000);
vsprintf(buf, va_alist, ap);
syslog(LOG_DEBUG, buf);
free(buf);
#else
vsyslog(LOG_NOTICE, va_alist, ap);
#endif
}
va_end(ap);
}
void Error(char *va_alist, ...)
{
va_list ap;
#ifdef va_dcl
va_start(ap);
#else
va_start(ap, va_alist);
#endif
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, va_alist, ap);
if (strrchr(va_alist, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
#ifdef va_dcl
char *buf = malloc(50000);
vsprintf(buf, va_alist, ap);
syslog(LOG_DEBUG, buf);
free(buf);
#else
vsyslog(LOG_ERR, va_alist, ap);
#endif
}
va_end(ap);
}
void Fatal(char *va_alist, ...)
{
va_list ap;
#ifdef va_dcl
va_start(ap);
#else
va_start(ap, va_alist);
#endif
if (interactive) {
fprintf(stderr, "%s: ", progName);
vfprintf(stderr, va_alist, ap);
if (strrchr(va_alist, '\n') == 0) {
fprintf(stderr, "\n");
}
} else {
#ifdef va_dcl
char *buf = malloc(50000);
vsprintf(buf, va_alist, ap);
syslog(LOG_DEBUG, buf);
free(buf);
#else
vsyslog(LOG_ERR, va_alist, ap);
#endif
}
va_end(ap);
exit(-1);
}
#endif
int
checkACL(char *name, Service *s)
{
ACLNode *acl;
acl = s->acl;
if ( acl ) {
while ( acl ) {
if ( strcmp(name, acl->name) == 0 ) {
/* match */
return(0);
}
acl = acl->next;
}
return(-1);
} else {
return(0);
}
}
static void Shutdown(Service *s, Todo *t, Sock *psock, fd_set *pfds)
{
Todo **tp = &s->todo;
Todo *n;
Sock *pcursock, *plastsock;
int waslast;
DebugPrint("shutdown connection on port %d using %d,%d",
s->port, psock->sslfd, psock->clearfd);
SSL_Close(psock->sslfd);
close(psock->clearfd);
FD_CLR(psock->clearfd, pfds);
FD_CLR(psock->sslfd, pfds);
waslast = 0;
/* unline psock from the sockets list */
plastsock = 0;
pcursock = t->sockets;
while ( pcursock ) {
if ( pcursock == psock ) {
if ( plastsock ) {
/* not head of list */
plastsock->next = pcursock->next;
} else {
t->sockets = pcursock->next;
if ( pcursock->next == 0 ) {
/* only element on list */
waslast = 1;
}
}
free(pcursock);
break;
}
plastsock = pcursock;
pcursock = plastsock->next;
}
/* if the above loop fell through then we were called incorrectly */
if ( !pcursock ) {
Fatal("Shutdown called with bad psock");
}
/* The last sock of this todo was closed, so get rid of todo struct */
if ( waslast ) {
DebugPrint("final shutdown of connection on port %d\n", s->port);
while ((n = *tp) != 0) {
if (n == t) {
if ( t->controlfd != -1 ) {
close( t->controlfd );
FD_CLR(t->controlfd, pfds);
}
*tp = t->next;
free(t);
return;
}
tp = &n->next;
}
}
}
/*
** Forward data from s2 to host specified in s. If asServer is true, then
** s2 is sslfd. If client, then s2 is clearfd.
*/
static Todo *SetupForwarder(Service *s, int s2, int asServer)
{
Todo *t;
int s3, rv;
u_long addr;
Sock *psock;
/* Setup forwarding agent */
s3 = socket(AF_INET, SOCK_STREAM, 0);
if (s3 < 0) {
Error("socket create failed: %s", strerror(errno));
return 0;
}
/* Connect to forwarding address */
rv = connect(s3, (struct sockaddr*)&s->forward, sizeof(s->forward));
if (rv < 0) {
addr = ntohl(s->forward.sin_addr.s_addr);
Error("forwarder connect failed to %d.%d.%d.%d:%d %s",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff,
ntohs(s->forward.sin_port),
strerror(errno));
SSL_Close(s2);
close(s3);
return 0;
}
addr = ntohl(s->forward.sin_addr.s_addr);
DebugPrint("forwarding %s data to %d.%d.%d.%d:%d using %d",
asServer ? "clear" : "ssl",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff,
ntohs(s->forward.sin_port), s3);
/* Ready to go... */
t = (Todo*) calloc(1, sizeof(Todo));
t->next = 0;
psock = (Sock*) calloc(1, sizeof(Sock));
if (asServer) {
psock->clearfd = s3;
psock->sslfd = s2;
} else { /* XXX this does not work */
psock->clearfd = s2;
psock->sslfd = s3;
}
psock->next = 0;
t->sockets = psock;
t->controlfd = -1;
return t;
}
int HandshakeCallback(int fd, void *data)
{
Todo *t;
Callback *callback;
Service *s;
Sock *psock;
char *peercn;
SECCertificate *cert;
callback = data;
s = callback->service;
t = callback->todo;
psock = callback->sock;
DebugPrint("handshake finished on %d:", fd);
DebugPrint("\tclear: %d", t->sockets->clearfd);
if (t->controlfd != -1)
DebugPrint("\tcontrol: %d", t->controlfd);
if (s->auth) {
cert = SSL_PeerCertificate(fd);
if (!cert) {
Error("Could not obtain peer certificate");
nfd = -1;
Shutdown(s, t, psock, &work);
free(callback);
return -1;
}
peercn = SEC_GetCommonName(&cert->subject);
if( peercn ) {
DebugPrint("peer authenticated as %s", peercn);
}
else {
DebugPrint("peer not authenticated");
}
if ( checkACL(peercn, s) ) {
Error("%s does not have access to port %d", peercn, s->port);
nfd = -1;
Shutdown(s, t, psock, &work);
free(callback);
return -1;
}
}
/* check to see if this todo has finished handshake */
FD_SET(t->sockets->clearfd, &work);
if (t->sockets->clearfd > nfd) {
nfd = t->sockets->clearfd;
}
if ( t->controlfd != -1 ) {
FD_SET(t->controlfd, &work);
if (t->controlfd > nfd) {
nfd = t->controlfd;
}
}
free(callback);
return 0;
}
/*
** Connect as a client to an ssl server out there somewhere.
*/
static Todo *ConnectClient(Service *s, int s2)
{
Todo *t;
/* Setup forwarding agent, calling internal function */
t = SetupForwarder(s, s2, SSLD_CLIENT);
return t;
}
/*
** Connect as a server to an ssl client out there somewhere.
*/
static Todo *ConnectServer(Service *s, int s2)
{
Todo *t;
int sv[2], rv, pid, val;
int ctrlsv[2];
Sock *psock;
switch (s->action) {
case FORWARDER:
t = SetupForwarder(s, s2, SSLD_SERVER);
break;
case EXECER:
/* Create socketpair to talk with child */
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
if (rv < 0) {
/*
** This might work later so just abandon the new connection
*/
Error("socketpair failed: %s", strerror(errno));
SSL_Close(s2);
return 0;
}
/* create a socketpair for the control connection */
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, ctrlsv);
if (rv < 0) {
/*
** This might work later so just abandon the new connection
*/
Error("control socketpair failed: %s", strerror(errno));
SSL_Close(s2);
close(sv[0]);
close(sv[1]);
return 0;
}
val = 1;
setsockopt(sv[0], SOL_SOCKET, SO_KEEPALIVE, (void*)&val, sizeof(val));
setsockopt(ctrlsv[0], SOL_SOCKET, SO_KEEPALIVE, (void*)&val, sizeof(val));
/* Fork child to process clear connection */
pid = fork();
if (pid < 0) {
/*
** Fork's might work later, so just abandon the new
** connection.
*/
Error("fork failed: %s", strerror(errno));
close(sv[0]);
close(sv[1]);
close(ctrlsv[0]);
close(ctrlsv[1]);
SSL_Close(s2);
return 0;
}
if (pid == 0) {
int i;
/* Child */
dup2(sv[0], 0);
dup2(sv[0], 1);
dup2(sv[0], 2);
dup2(ctrlsv[0], 3); /* control socket is fd 3 */
for (i = 4; i < max_files; i++) {
close(i);
}
signal(SIGPIPE, SIG_DFL);
execv(s->file, s->avp);
Error("execv failed: %s", strerror(errno));
exit(-1);
}
/* Parent */
close(sv[0]);
close(ctrlsv[0]);
DebugPrint("feeding clear data to %s (pid %d) using %d",
s->file, pid, sv[1]);
/* Create Todo item to shovel the i/o around */
t = (Todo *) calloc(1, sizeof(Todo));
psock = (Sock *) calloc(1, sizeof(Sock));
t->controlfd = ctrlsv[1];
DebugPrint("control fd is %d", t->controlfd);
t->cbufoff = 0;
t->sockets = psock;
psock->clearfd = sv[1];
psock->sslfd = s2;
break;
}
return t;
}
/* This just waits for child process to change state */
static void Reaper(int i)
{
int s;
while (waitpid(-1, &s, WNOHANG) > 0)
;
}
/* Initialize the key & certificate database stuff */
void ssld_init(Service *s, char *keyDir)
{
SECKeyDBHandle *keydb;
SECPrivateKey *key;
SECCertificate *cert;
int errsave;
int rv;
SEC_Init();
/* Set up default directory for databases */
keyDir = SECU_ConfigDirectory(keyDir);
/* Open the key pair database */
keydb = SEC_OpenKeyDB(SECU_DatabaseFileName(xpKeyDB));
if (keydb == NULL) {
Fatal("Unable to read key file (%s)", SECU_ErrorString(XP_GetError()));
}
SEC_SetDefaultKeyDB(keydb);
/*
** Get the server private key from the database, prompting for
** the password.
*/
key = SECU_GetPrivateDongleKey(keydb, s->keyname, keyDir);
errsave = XP_GetError();
/* Handle failure to get key, if any */
if (!key) {
if (errsave == SEC_ERROR_BAD_PASSWORD)
Fatal("Bad password");
else if (errsave > 0)
Fatal("Unable to read key file (%s)", SECU_ErrorString(errsave));
else if (errsave == SEC_ERROR_BAD_DATABASE)
Fatal("Unable to get key from database (%s)", SECU_ErrorString(errsave));
else
Fatal("%s", SECU_ErrorString(errsave));
}
/* Open the server cert database */
cert_db_handle = (SECCertDBHandle *)DS_Zalloc(sizeof(SECCertDBHandle));
if (!cert_db_handle) {
Fatal("Could not allocate database handle");
}
/* XXX - this should not be required for SSL */
SEC_SetDefaultCertDB(cert_db_handle);
/*rv = SEC_OpenCertDB(cert_db_handle, SECU_DatabaseFileName(xpCertDB), TRUE );*/
rv = SEC_OpenCertDB(cert_db_handle, NULL, FALSE); /* Open default database, readonly false */
if (rv)
Fatal("Open of server cert database failed (%s)",
SECU_ErrorString(XP_GetError()));
/* Read the server cert from the database */
cert = SEC_FindCertByNickname(cert_db_handle, s->certname);
if (!cert)
Fatal("Could not read server certificate (%s)",
SECU_ErrorString(XP_GetError()));
rv = SEC_CertTimesValid(cert);
if (rv)
Fatal("Server certificate is not valid");
rv = SSL_ConfigSecureServer(&cert->derCert, key,
SEC_CertChainFromCert(cert_db_handle, cert),
SEC_GetSSLCACerts(cert_db_handle));
if (rv)
Fatal("Server key/certificate is bad (%s)",
SECU_ErrorString(XP_GetError()));
rv = SSL_ConfigServerSessionIDCache(0, 0, 0, 0);
if (rv) {
if (XP_GetError() == ENOSPC)
Fatal("Config of server nonce cache failed, "
"out of disk space! Make more room in /tmp "
"and try again.");
else
Fatal("Config of server nonce cache failed (%s)",
SECU_ErrorString(XP_GetError()));
}
SEC_DestroyPrivateKey(key);
}
static void Usage(char *progName)
{
printf("Usage: %s [-d keydir] [-i] [-D] [-c conffile] [-C chrootdir]\n",
progName);
printf("%-20s directory for the key & certficate databases and\n",
"-d keydir");
printf("%-20s \tconfiguration file (default is %s)\n", "", defaultKeyDir);
printf("%-20s run in interactive mode (don't fork)\n",
"-i");
printf("%-20s enable debugging\n",
"-D");
printf("%-20s specify the config file (default is %s)\n",
"-c conffile", defaultConfFile);
printf("%-20s chroot to the named directory\n",
"-C chrootdir");
exit(-1);
}
void main(int argc, char **argv)
{
char *newRoot, *keyDir, *conf;
int o, sfd, rv, bi;
Service *s, *servs;
int shutdown;
Sock *psock, *nsock;
Callback *callback;
int lport;
short sport;
int fd;
struct rlimit rlp;
progName = strrchr(argv[0], '/');
progName = progName ? progName+1 : argv[0];
bi = 0;
newRoot = 0;
keyDir = 0;
conf = 0;
while ((o = getopt(argc, argv, "c:C:Did:")) != EOF) {
switch (o) {
case '?':
Usage(progName);
case 'C':
newRoot = strdup(optarg);
break;
case 'c':
conf = strdup(optarg);
break;
case 'D':
dbg = 1;
bi = 1;
break;
case 'i':
bi = 1;
break;
case 'd':
keyDir = strdup(optarg);
break;
}
}
/* If no keyDir specified, get environ variable or set to default */
if (!keyDir) {
keyDir = SECU_DefaultSSLDir();
if (!keyDir)
keyDir = defaultKeyDir;
}
/* If no conf specified, set to environ variable or to default */
if (!conf) {
conf = getenv("SSLD_CONF");
if (!conf) {
conf = SECU_AppendFilenameToDir(keyDir, defaultConfFile);
}
else if (conf[0] == '/')
;
else {
conf = SECU_AppendFilenameToDir(keyDir, conf);
}
}
/* else conf was specified in command line */
else {
if (conf[0] == '/')
;
else
conf = SECU_AppendFilenameToDir(keyDir, conf);
}
if (newRoot) {
rv = chroot(newRoot);
if (rv < 0) {
Fatal("chroot to %s failed", newRoot);
}
}
/* initialize PR before ReadConfFile, since it calls gethostbyname */
PR_Init("ssld", 1, 1, 0);
rv = getrlimit(RLIMIT_NOFILE, &rlp);
if (rv >= 0) {
max_files = rlp.rlim_cur;
Notice("Max file limit: %d\n", max_files);
}
DebugPrint("using database directory: %s", keyDir);
/*
** Process config file
*/
servs = ReadConfFile(conf);
if (!servs) {
Notice("unable to parse config file %s", conf);
exit(-1);
}
/*
** Initialize libsec and certificate/key database
** XXX This is ONLY using the first Service struct returned by
** ReadConfFile above. Need to prompt for all passwords in beginning
** during ssld_init, not just first one. /dk
*/
ssld_init(servs, keyDir);
/*
** Create sockets for listening
*/
FD_ZERO(&work);
nfd = 0;
for (s = servs; s; s = s->next) {
struct sockaddr_in sin;
int s1, val;
/* Setup sockets for listening */
s1 = SSL_Socket(AF_INET, SOCK_STREAM, 0);
if (s1 < 0) Fatal("socket creation failed: %s",
SECU_ErrorString(XP_GetError()));
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_family = AF_INET;
sin.sin_port = s->port;
val = 1;
SSL_SetSockOpt(s1, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(val));
rv = SSL_Enable(s1, SSL_SECURITY, 1);
if (rv < 0) {
Fatal("%s: could not enable ssl socket", progName);
} else {
DebugPrint("%s: enabled security on %d", progName, s1);
}
rv = SSL_Enable(s1, SSL_HANDSHAKE_AS_SERVER, 1);
if (rv < 0) {
Fatal("%s: could not enable handshake as server", progName);
}
rv = SSL_Bind(s1, (struct sockaddr*) &sin, sizeof(sin));
if (rv < 0) Fatal("socket bind to %d failed: %s",
ntohs(s->port), SECU_ErrorString(XP_GetError()));
rv = SSL_Listen(s1, 5);
if (rv < 0) Fatal("socket listen failed: %s",
SECU_ErrorString(XP_GetError()));
s->listen = s1;
FD_SET(s1, &work);
if (s1 > nfd) nfd = s1;
/* Set the certificate */
rv = SSL_AuthCertificateHook(s1, SSL_AuthCertificate,
(void *)cert_db_handle);
if (rv)
DebugPrint("%s: could not set certificate auth hook", progName);
}
sfd = nfd;
interactive = bi;
if (!interactive) {
int pid;
openlog(progName, LOG_PID, LOG_DAEMON);
pid = fork();
if (pid < 0) Fatal("fork failed: %s", SECU_ErrorString(errno));
if (pid != 0) {
/* Parent exits */
exit(0);
}
setsid();
}
signal(SIGPIPE, SIG_IGN);
signal(SIGCLD, Reaper);
/*
** Run daemon loop waiting for a connections to come in and servicing
** i/o operations.
*/
for (;;) {
fd_set rd;
rd = work;
DebugPrint("to SELECT: 0x%08x", rd.fds_bits[0]);
rv = select(nfd+1, &rd, 0, 0, 0);
if (rv < 0) {
if ( errno == EINTR ) {
continue;
} else {
Fatal("select failed, errno = %d", errno);
}
}
DebugPrint("from SELECT: 0x%08x [%d]", rd.fds_bits[0], rv);
/* Figure out what happened */
for (s = servs; s; s = s->next) {
Todo *t, *nxt;
Sock *psock;
/* See if listen socket is ready for another connection */
if (FD_ISSET(s->listen, &rd)) {
int al, s2;
struct sockaddr_in sin;
DebugPrint("connection ready on %d", s->listen);
/* Connection came in for this service */
al = sizeof(sin);
if (s->auth) {
rv = SSL_Enable(s->listen, SSL_REQUEST_CERTIFICATE, 1);
if ( rv ) {
Error("Could not enable client auth");
}
}
rv = SSL_Enable(s->listen, SSL_DELAYED_HANDSHAKE, 1);
if (rv) {
DebugPrint("could not enable delayed handshake");
}
/* Accept this connection (it was enabled above) */
s2 = SSL_Accept(s->listen, (struct sockaddr*) &sin, &al);
if (s2 < 0) {
Error("accept failed on %d: %s", s->listen,
SECU_ErrorString(XP_GetError()));
} else {
u_long addr = ntohl(sin.sin_addr.s_addr);
DebugPrint("ready for %s data from %d.%d.%d.%d:%d on %d",
s->client ? "clear" : "ssl",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff,
s->port, s2);
if (s->client) {
t = ConnectClient(s,s2); /* XXX client is broken */
} else {
t = ConnectServer(s, s2);
}
if (t) {
FD_SET(t->sockets->sslfd, &work);
if (t->sockets->sslfd > nfd) {
nfd = t->sockets->sslfd;
}
t->next = s->todo;
s->todo = t;
callback = (Callback *)calloc(1, sizeof(Callback));
callback->service = s;
callback->todo = t;
callback->sock = t->sockets;
rv = SSL_HandshakeCallback(s2, HandshakeCallback,
(void *)callback);
if (rv)
DebugPrint("error in set handshake callback on %d",
s2);
}
}
}
for (t = s->todo; t; t = nxt) {
char buf[8192];
nxt = t->next;
/* is there anything on the control channel for this client */
if ( ( t->controlfd >= 0 ) && FD_ISSET(t->controlfd, &rd) ) {
struct sockaddr_in dstsin;
short port;
long addr;
rv = read(t->controlfd, &t->controlbuf[t->cbufoff],
sizeof(t->controlbuf) - t->cbufoff);
if ( rv > 0 ) { /* read returns data */
/* bump buffer fill count */
t->cbufoff += rv;
if ( t->cbufoff == sizeof(t->controlbuf) ) {
/* buffer is full */
struct ssld_proto *pctrlbuf;
pctrlbuf = (struct ssld_proto *)t->controlbuf;
t->cbufoff = 0;
if ( pctrlbuf->cmd == SSLD_CONNECT ) {
/* extract port and address from buffer */
memcpy(&port, &pctrlbuf->port,
sizeof(pctrlbuf->port));
memcpy(&addr, &pctrlbuf->addr,
sizeof(pctrlbuf->addr));
DebugPrint("SSLD_CONNECT request on %d(control) for %d.%d.%d.%d:%d",
t->controlfd,
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff,
port);
/* destination address */
dstsin.sin_family = AF_INET;
dstsin.sin_port = port;
dstsin.sin_addr.s_addr = addr;
/* set up new Sock structure */
psock = calloc(1, sizeof(Sock));
lport = IPPORT_RESERVED;
fd = rresvport(&lport);
if ( fd < 0 ) {
DebugPrint("first rresvport failed\n");
}
sport = (short)lport;
/* listen for his connect */
rv = listen(fd, 1);
if ( rv < 0 ) {
DebugPrint("listen failed\n");
}
/* send our port to the client */
memcpy(&pctrlbuf->port, &sport,
sizeof(pctrlbuf->port));
write(t->controlfd, pctrlbuf, SSLD_PROTO_LEN);
DebugPrint("SSLD_CONNECT returns port %d",
sport);
psock->clearfd = accept(fd, 0, 0);
if ( psock->clearfd < 0 ) {
DebugPrint("accept failed");
} else {
DebugPrint("accept succeeded on %d(clear)",
psock->clearfd);
}
close(fd);
/* get reserved port for destination */
fd = rresvport(&lport);
if ( fd < 0 ) {
DebugPrint("second rresvport failed\n");
}
/* make it a groovy ssl socket */
rv = SSL_Import(fd);
if (rv < 0)
DebugPrint("unable to import new sock");
/* set up ssl on this socket */
rv = SSL_Enable(fd, SSL_SECURITY, 1);
if (rv < 0)
DebugPrint("enable failed\n");
rv = SSL_Enable(fd, SSL_HANDSHAKE_AS_SERVER, 1);
if (rv < 0)
DebugPrint("handshake enable failed\n");
rv = SSL_AuthCertificateHook(fd, SSL_AuthCertificate,
(void *)SEC_GetDefaultCertDB());
if (s->auth) {
rv = SSL_Enable(fd, SSL_REQUEST_CERTIFICATE, 1);
if (rv)
DebugPrint("cert req failed on %d(ssl)", fd);
}
callback = (Callback *)calloc(1, sizeof(Callback));
callback->service = s;
callback->sock = psock;
callback->todo = t;
rv = SSL_HandshakeCallback(fd, HandshakeCallback, (void *)callback);
if (rv)
DebugPrint("handshake callback failed on %d(ssl)", fd);
/* now connect */
rv = SSL_Connect(fd, &dstsin, sizeof(dstsin));
if ( rv < 0 ) {
DebugPrint("connect failed on %d(ssl)", fd);
} else {
DebugPrint("connect succeeded on %d(ssl)",fd);
}
psock->sslfd = fd;
/* link into list */
psock->next = t->sockets;
t->sockets = psock;
/* add to work sockets */
FD_SET(psock->sslfd, &work);
if( psock->sslfd > nfd ) {
nfd = psock->sslfd;
}
DebugPrint("Set up back channel from %d(clear) to %d(ssl)", psock->clearfd, psock->sslfd);
} else if ( pctrlbuf->cmd == SSLD_GETPEER ) {
struct sockaddr_in peersin;
int peerlen = sizeof(struct sockaddr_in);
/* XXX - need better fd */
DebugPrint("SSLD_GETPEER request from %d(control)",
t->controlfd);
if ( getpeername(t->sockets->sslfd,
(struct sockaddr *)&peersin,
&peerlen) < 0 ) {
DebugPrint("getpeername failed");
}
memcpy(&pctrlbuf->port, &peersin.sin_port,
sizeof(pctrlbuf->port));
memcpy(&pctrlbuf->addr,
&peersin.sin_addr.s_addr,
sizeof(pctrlbuf->addr));
addr = peersin.sin_addr.s_addr;
port = peersin.sin_port;
DebugPrint("SSLD_GETPEER of %d(ssl) is %d.%d.%d.%d:%d",
t->sockets->sslfd,
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff,
port);
write(t->controlfd, pctrlbuf, SSLD_PROTO_LEN);
}
}
} else {
FD_CLR(t->controlfd, &work);
if (rv < 0) {
DebugPrint("control read (%d) returned %d (error=%d)",
t->controlfd, rv, errno);
} else
DebugPrint("control read (%d) EOF", t->controlfd);
}
}
/* See if i/o sockets are ready for work to be done */
for ( psock = t->sockets; psock; psock = nsock ) {
nsock = psock->next;
shutdown = 0;
if ( (psock->clearfd >= 0) &&
FD_ISSET(psock->clearfd, &rd) ) {
int rv;
int nb;
nb = read(psock->clearfd, buf, sizeof(buf));
DebugPrint("clear read (%d), %d bytes: [0x%02x]",
psock->clearfd, nb, buf[0]);
if (nb <= 0) {
DebugPrint("SHUTDOWN (clear read returned %d)",
nb);
shutdown = 1;
nfd = -1;
} else {
rv = SSL_Write(psock->sslfd, buf, nb);
DebugPrint("ssl write (%d), %d of %d bytes",
psock->sslfd, rv, nb);
if (rv < 0) {
DebugPrint("SHUTDOWN (ssl write returned %d)",
rv);
shutdown = 1;
nfd = -1;
}
}
}
if ( ( !shutdown ) &&
(psock->sslfd >= 0) &&
FD_ISSET(psock->sslfd, &rd) ) {
int rv;
int nb;
nb = SSL_Read(psock->sslfd, buf, sizeof(buf));
DebugPrint("ssl read (%d), %d bytes: [0x%02x]",
psock->sslfd, nb, buf[0]);
if (nb < 0) {
if (XP_GetError() == EWOULDBLOCK)
continue;
DebugPrint("SHUTDOWN (ssl read returned %d), %s", nb, SECU_ErrorString(XP_GetError()));
shutdown = 1;
nfd = -1;
} else
if (nb == 0) {
DebugPrint("SHUTDOWN (ssl read returned %d)", nb);
shutdown = 1;
nfd = -1;
} else {
rv = write(psock->clearfd, buf, nb);
DebugPrint("clear write (%d), %d of %d bytes",
psock->clearfd, rv, nb);
if (rv < 0) {
DebugPrint("SHUTDOWN (clear write returned %d)", rv);
shutdown = 1;
nfd = -1;
}
}
}
if ( shutdown ) {
Shutdown(s, t, psock, &work);
}
}
}
}
if (nfd < 0) {
/* Recompute nfd value */
for (s = servs; s; s = s->next) {
Todo *t;
if (s->listen > nfd) nfd = s->listen;
for (t = s->todo; t; t = t->next) {
for ( psock = t->sockets; psock; psock = psock->next ) {
if (psock->sslfd > nfd) nfd = psock->sslfd;
if (psock->clearfd > nfd) nfd = psock->clearfd;
}
}
}
}
}
}