Milán Major

netscape-revival

1 branch
Code

security/lib/ssl/sslsocks.c

#include "xp.h"
#include "cert.h"
#include "xp_file.h"
#include "ssl.h"
#include "sslimpl.h"


extern int XP_ERRNO_EISCONN;
extern int XP_ERRNO_EBADF;
extern int XP_ERRNO_ECONNREFUSED;
extern int XP_ERRNO_EINVAL;
extern int XP_ERRNO_EIO;
extern int XP_ERRNO_EWOULDBLOCK;

#ifdef XP_UNIX
#define SOCKS_FILE	"/etc/socks.conf"
#endif
#ifdef XP_MAC
#define SOCKS_FILE NULL
#endif
#ifdef XP_WIN
#define SOCKS_FILE  NULL
#endif

#define SOCKS_VERSION	4

#define DEF_SOCKD_PORT	1080

#define SOCKS_CONNECT	1
#define SOCKS_BIND	2

#define SOCKS_RESULT	90
#define SOCKS_FAIL	91
#define SOCKS_NO_IDENTD	92 /* Failed to connect to Identd on client machine */
#define SOCKS_BAD_ID	93 /* Client's Identd reported a different user-id */

#define MAKE_IN_ADDR(a,b,c,d) \
    htonl(((unsigned long)(a) << 24) | ((unsigned long)(b) << 16) | \
	  ((c) << 8) | (d))

typedef struct SocksConfItemStr SocksConfItem;

struct SocksConfItemStr {
    char direct;
    unsigned long daddr;
    unsigned long dmask;
    int op;
    unsigned short port;
    SocksConfItem *next;
};

/* Values for op */
#define OP_LESS		1
#define OP_EQUAL	2
#define OP_LEQUAL	3
#define OP_GREATER	4
#define OP_NOTEQUAL	5
#define OP_GEQUAL	6
#define OP_ALWAYS	7

static unsigned long ourHost;
static SocksConfItem *ssl_socks_confs;

int ssl_CreateSocksInfo(SSLSocket *ss)
{
    SSLSocksInfo *si;

    if (ss->socks) {
	/* Already been done */
	return 0;
    }

    si = (SSLSocksInfo*) PORT_ZAlloc(sizeof(SSLSocksInfo));
    if (si) {
	ss->socks = si;
	if (!ss->gather) {
	    ss->gather = ssl_NewGather();
	    if (!ss->gather) {
		return -1;
	    }
	}
	return 0;
    }
    return -1;
}

int ssl_CopySocksInfo(SSLSocket *ss, SSLSocket *os)
{
    int rv;

#ifdef __cplusplus
    os = os;
#endif
    rv = ssl_CreateSocksInfo(ss);
    return rv;
}

void ssl_DestroySocksInfo(SSLSocksInfo *si)
{
    if (si) {
	PORT_Free(si);
    }
}

#if defined(XP_UNIX) || defined(XP_MAC)
#include "prnetdb.h"
#else
#define PRHostEnt struct hostent
#define PR_NETDB_BUF_SIZE 5
#endif
static int GetOurHost(void)
{
    char name[100];
    PRHostEnt hpbuf, *hp;
    char dbbuf[PR_NETDB_BUF_SIZE];

    gethostname(name, sizeof(name));
#if defined(XP_UNIX) || defined(XP_MAC)
    hp = PR_gethostbyname(name, &hpbuf, dbbuf, sizeof(dbbuf), 0);
#else
    hp = gethostbyname(name);
#endif
    if (hp) {
	ourHost = *(unsigned long *)hp->h_addr;
    } else {
	/* Total lossage! */
	return -1;
    }
    return 0;
}

/*
** Setup default SocksConfItem list so that loopback is direct, things to the
** same subnet (?) address are direct, everything else uses sockd
*/
static void BuildDefaultConfList(void)
{
    SocksConfItem *ci;
    SocksConfItem **lp;

    /* Put loopback onto direct list */
    lp = &ssl_socks_confs;
    ci = (SocksConfItem*) PORT_ZAlloc(sizeof(SocksConfItem));
    if (!ci) {
	return;
    }
    ci->direct = 1;
    ci->daddr = MAKE_IN_ADDR(127,0,0,1);
    ci->dmask = MAKE_IN_ADDR(255,255,255,255);
    ci->op = OP_ALWAYS;
    *lp = ci;
    lp = &ci->next;

    /* Put our hosts's subnet onto direct list */
    ci = (SocksConfItem*) PORT_ZAlloc(sizeof(SocksConfItem));
    if (!ci) {
	return;
    }
    ci->direct = 1;
    ci->daddr = htonl((ntohl(ourHost) & ~0xff) | 0xff);
    ci->dmask = MAKE_IN_ADDR(255,255,255,0);
    ci->op = OP_ALWAYS;
    *lp = ci;
    lp = &ci->next;

    /* Everything else goes to sockd */
    ci = (SocksConfItem*) PORT_ZAlloc(sizeof(SocksConfItem));
    if (!ci) {
	return;
    }
    ci->daddr = MAKE_IN_ADDR(255,255,255,255);
    ci->op = OP_ALWAYS;
    *lp = ci;
}

static int FragmentLine(char *cp, char **argv, int maxargc)
{
    int argc = 0;
    char *save;
    char ch;

    save = cp;
    for (; (ch = *cp) != 0; cp++) {
	if ((ch == '#') || (ch == '\n')) {
	    /* Done */
	    break;
	}
	if (ch == ':') {
	    break;
	}
	if ((ch == ' ') || (ch == '\t')) {
	    /* Seperator. see if it seperated anything */
	    if (cp - save > 0) {
		/* Put a null at the end of the word */
		*cp = 0;
		argc++;
		*argv++ = save;
		SSL_TRC(20, ("%d: SSL: argc=%d word=\"%s\"",
			     SSL_GETPID(), argc, save));
		if (argc == maxargc) {
		    return argc;
		}
	    }
	    save = cp + 1;
	}
    }
    if (cp - save > 0) {
	*cp = 0;
	argc++;
	*argv = save;
	SSL_TRC(20, ("%d: SSL: argc=%d word=\"%s\"",
		     SSL_GETPID(), argc, save));
    }
    return argc;
}

/* XXX inet_addr? */
static char *ConvertOne(char *cp, unsigned char *rvp)
{
    char *s = PORT_Strchr(cp, '.');
    if (s) {
	*s = 0;
    }
    *rvp = PORT_Atoi(cp) & 0xff;
    return s ? s+1 : cp;
}

static unsigned long ConvertAddr(char *buf)
{
    unsigned char b0, b1, b2, b3;
    unsigned long addr;

    buf = ConvertOne(buf, &b0);
    buf = ConvertOne(buf, &b1);
    buf = ConvertOne(buf, &b2);
    buf = ConvertOne(buf, &b3);
    addr = ((unsigned long)b0 << 24) |
	((unsigned long)b1 << 16) |
	((unsigned long)b2 << 8) |
	(unsigned long)b3;

    return htonl(addr);
}

static int ReadConfFile(void)
{
    int rv;

    rv = GetOurHost();
    if (rv < 0) {
	/* If we can't figure out our host id, use socks. Loser! */
	return -1;
    }

    {
	SocksConfItem *ci;
	SocksConfItem **lp;
	XP_File fp;
	char *file = SOCKS_FILE;
	int op, direct, port = 0, lineNumber = 0;

	fp = XP_FileOpen(file, xpSocksConfig, XP_FILE_READ);
	if (!fp) {
	    BuildDefaultConfList();
	    return 0;
	}

	/* Parse config file and generate config item list */
	lp = &ssl_socks_confs;
	for (;;) {
	    char buf[1000];
	    char *s, *argv[10];
	    int argc;

	    s = XP_FileReadLine(buf, sizeof(buf), fp);
	    if (!s) {
		break;
	    }
	    lineNumber++;
	    argc = FragmentLine(buf, argv, 10);
	    if (argc < 3) {
		if (argc == 0) {
		    /* must be a comment/empty line */
		    continue;
		}
#ifdef XP_UNIX
		fprintf(stderr, "%s:%d: bad config line\n",
			file, lineNumber);
#endif
		continue;
	    }
	    if (PORT_Strcmp(argv[0], "direct") == 0) {
		direct = 1;
	    } else if (PORT_Strcmp(argv[0], "sockd") == 0) {
		direct = 0;
	    } else {
#ifdef XP_UNIX
		fprintf(stderr, "%s:%d: bad command: \"%s\"\n",
			file, lineNumber, argv[0]);
#endif
		continue;
	    }

	    /* Look for port spec */
	    op = OP_ALWAYS;
	    if (argc > 4) {
		if (PORT_Strcmp(argv[3], "lt") == 0) {
		    op = OP_LESS;
		} else if (PORT_Strcmp(argv[3], "eq") == 0) {
		    op = OP_EQUAL;
		} else if (PORT_Strcmp(argv[3], "le") == 0) {
		    op = OP_LEQUAL;
		} else if (PORT_Strcmp(argv[3], "gt") == 0) {
		    op = OP_GREATER;
		} else if (PORT_Strcmp(argv[3], "neq") == 0) {
		    op = OP_NOTEQUAL;
		} else if (PORT_Strcmp(argv[3], "ge") == 0) {
		    op = OP_GEQUAL;
		} else {
#ifdef XP_UNIX
		    fprintf(stderr, "%s:%d: bad comparison op: \"%s\"\n",
			    file, lineNumber, argv[3]);
#endif
		    continue;
		}
		port = PORT_Atoi(argv[4]);
	    }

	    ci = (SocksConfItem*) PORT_ZAlloc(sizeof(SocksConfItem));
	    if (!ci) {
		break;
	    }
	    ci->direct = direct;
	    ci->daddr = ConvertAddr(argv[1]);
	    ci->dmask = ConvertAddr(argv[2]);
	    ci->op = op;
	    ci->port = port;
	    SSL_TRC(10, ("%d: SSL: line=%d direct=%d addr=%d.%d.%d.%d mask=%d.%d.%d.%d op=%d port=%d",
			 SSL_GETPID(), lineNumber, ci->direct,
			 (ntohl(ci->daddr) >> 24) & 0xff,
			 (ntohl(ci->daddr) >> 16) & 0xff,
			 (ntohl(ci->daddr) >> 8) & 0xff,
			 (ntohl(ci->daddr) >> 0) & 0xff,
			 (ntohl(ci->dmask) >> 24) & 0xff,
			 (ntohl(ci->dmask) >> 16) & 0xff,
			 (ntohl(ci->dmask) >> 8) & 0xff,
			 (ntohl(ci->dmask) >> 0) & 0xff,
			 ci->op, ci->port));
	    *lp = ci;
	    lp = &ci->next;
	}
    }

    if (!ssl_socks_confs) {
	/* Empty file. Fix it for the user */
	BuildDefaultConfList();
    }
    return 0;
}

static int ChooseAddress(SSLSocket *ss, struct sockaddr_in *direct)
{
    unsigned long dstAddr;
    unsigned short dstPort;
    SocksConfItem *ci;
    int rv;

    if (!ssl_socks_confs) {
	rv = ReadConfFile();
	if (rv) {
	    return rv;
	}
    }

    /*
    ** Scan socks config info and look for a direct match or a force to
    ** use the sockd. Bail on first hit.
    */
    dstAddr = direct->sin_addr.s_addr;
    dstPort = ntohs(direct->sin_port);
    ci = ssl_socks_confs;
    while (ci) {
	SSL_TRC(10,
		("%d: SSL[%d]: match, direct=%d daddr=0x%x mask=0x%x op=%d port=%d",
		 SSL_GETPID(), ss->fd, ci->direct, ci->daddr,
		 ci->dmask, ci->op, ci->port));
	if ((ci->daddr & ci->dmask)  == (dstAddr & ci->dmask)) {
	    int portMatch = 0;
	    switch (ci->op) {
	      case OP_LESS:	portMatch = dstPort < ci->port; break;
	      case OP_EQUAL:	portMatch = dstPort == ci->port; break;
	      case OP_LEQUAL:	portMatch = dstPort <= ci->port; break;
	      case OP_GREATER:	portMatch = dstPort > ci->port; break;
	      case OP_NOTEQUAL:	portMatch = dstPort != ci->port; break;
	      case OP_GEQUAL:	portMatch = dstPort >= ci->port; break;
	      case OP_ALWAYS:	portMatch = 1; break;
	    }
	    if (portMatch) {
		SSL_TRC(10, ("%d: SSL[%d]: socks config match",
			     SSL_GETPID(), ss->fd));
		return ci->direct;
	    }
	}
	ci = ci->next;
    }
    SSL_TRC(10, ("%d: SSL[%d]: socks config: no match",
		 SSL_GETPID(), ss->fd));
    return 0;
}

/*
** Find port # and host # of socks daemon. Use info in ss->socks struct
** when valid. If not valid, try to figure it all out.
*/
static int FindDaemon(SSLSocket *ss, struct sockaddr_in *out)
{
    SSLSocksInfo *si;
    unsigned short port;
    unsigned long host;

    PORT_Assert(ss->socks != 0);
    si = ss->socks;

    /* For now, assume we are using the socks daemon */
    host = si->sockdHost;
    port = si->sockdPort;
#ifdef XP_UNIX
    if (!port) {
	static char firstTime = 1;
	static unsigned short sockdPort;

	if (firstTime) {
	    struct servent *sp;

	    firstTime = 0;
	    sp = getservbyname("socks", "tcp");
	    if (sp) {
		sockdPort = sp->s_port;
	    } else {
		SSL_TRC(10, ("%d: SSL[%d]: getservbyname of (socks,tcp) fails",
			     SSL_GETPID(), ss->fd));
	    }
	}
	port = sockdPort;
    }
#endif
    if (!port) {
	port = DEF_SOCKD_PORT;
    }
    if (host == 0) {
	SSL_TRC(10, ("%d: SSL[%d]: no socks server found",
		     SSL_GETPID(), ss->fd));
	PORT_SetError(XP_ERRNO_EINVAL);
	return -1;
    }

    /* We know the ip addr of the socks server */
    out->sin_family = AF_INET;
    out->sin_port = htons(port);
    out->sin_addr.s_addr = host;
    SSL_TRC(10, ("%d: SSL[%d]: socks server at %d.%d.%d.%d:%d",
		 SSL_GETPID(), ss->fd,
		 (ntohl(out->sin_addr.s_addr) >> 24) & 0xff,
		 (ntohl(out->sin_addr.s_addr) >> 16) & 0xff,
		 (ntohl(out->sin_addr.s_addr) >> 8) & 0xff,
		 (ntohl(out->sin_addr.s_addr) >> 0) & 0xff,
		 port));
    return 0;
}

/*
** Send our desired address and our user name to the socks daemon.
*/
static int SayHello(SSLSocket *ss, int cmd, struct sockaddr_in *sa, char *user)
{
    int rv, len;
    unsigned char msg[8];
    unsigned short port;
    unsigned long host;

    /* Send dst message to sockd */
    port = sa->sin_port;
    host = sa->sin_addr.s_addr;
    msg[0] = SOCKS_VERSION;
    msg[1] = cmd;
    PORT_Memcpy(msg+2, &port, 2);
    PORT_Memcpy(msg+4, &host, 4);
    SSL_TRC(10, ("%d: SSL[%d]: socks real dest=%d.%d.%d.%d:%d",
		 SSL_GETPID(), ss->fd, msg[4], msg[5], msg[6], msg[7],
		 port));

    rv = ssl_DefSend(ss, msg, sizeof(msg), 0);
    if (rv < 0) {
	goto io_error;
    }

    /* Send src-user message to sockd */
    len = strlen(user)+1;
    rv = ssl_DefSend(ss, user, len, 0);
    if (rv < 0) {
	goto io_error;
    }
    return 0;

  io_error:
    SSL_TRC(10, ("%d: SSL[%d]: socks, io error saying hello to sockd errno=%d",
		 SSL_GETPID(), ss->fd, PORT_GetError()));
    return -1;
}

static int GetDst(SSLSocket *ss)
{
    SSLGather *gs;
    unsigned char *msg, cmd;

    PORT_Assert(ss->gather != 0);
    gs = ss->gather;

    msg = gs->buf.buf;
    cmd = msg[1];
    SSL_TRC(10, ("%d: SSL[%d]: socks result: cmd=%d",
		 SSL_GETPID(), ss->fd, cmd));
    PORT_Memcpy(&ss->socks->destPort, msg+2, 2);
    PORT_Memcpy(&ss->socks->destHost, msg+4, 4);

    /* Check status back from sockd */
    switch (cmd) {
      case SOCKS_FAIL:
      case SOCKS_NO_IDENTD:
      case SOCKS_BAD_ID:
	SSL_DBG(("%d: SSL[%d]: sockd returns an error: %d",
		 SSL_GETPID(), ss->fd, cmd));
	PORT_SetError(XP_ERRNO_ECONNREFUSED);
	return -1;

      default:
	break;
    }

    /* All done */
    SSL_TRC(1, ("%d: SSL[%d]: using sockd at %d.%d.%d.%d",
		SSL_GETPID(), ss->fd,
		(ntohl(ss->socks->sockdHost) >> 24) & 0xff,
		(ntohl(ss->socks->sockdHost) >> 16) & 0xff,
		(ntohl(ss->socks->sockdHost) >> 8) & 0xff,
		(ntohl(ss->socks->sockdHost) >> 0) & 0xff));
    ss->handshake = 0;
    ss->nextHandshake = 0;
    ss->gather->recordLen = 0;
    return 0;
}

static int Gather(SSLSocket *ss)
{
    int rv;

    rv = ssl_GatherData(ss, ss->gather, 0);
    if (rv <= 0) {
	PORT_SetError(rv < 0 ? XP_SOCK_ERRNO : XP_ERRNO_EIO);
	return -1;
    }
    ss->handshake = 0;
    return 0;
}

static int StartGather(SSLSocket *ss)
{
    int rv;

    ss->handshake = Gather;
    ss->nextHandshake = GetDst;
    rv = ssl_StartGatherBytes(ss, ss->gather, 8);
    if (rv <= 0) {
	if (rv == 0) {
	    /* Unexpected EOF */
	    PORT_SetError(XP_ERRNO_EIO);
	    return -1;
	}
	return rv;
    }
    ss->handshake = 0;
    return 0;
}

/************************************************************************/


/* BSDI ain't got no cuserid() */
#ifdef __386BSD__
#include <pwd.h>
char *bsdi_cuserid(char *b)
{
    struct passwd *pw = getpwuid(getuid());

    if (!b) return pw ? pw->pw_name : NULL;

    if (!pw || !pw->pw_name)
	b[0] = '\0';
    else
	strcpy(b, pw->pw_name);
    return b;
}
#endif


int ssl_SocksConnect(SSLSocket *ss, const void *sa, int salen)
{
    int rv, err, direct;
    struct sockaddr_in daemon, *sip;
    char *user;

#ifdef __cplusplus
    salen = salen;
#endif

    /* Figure out where to connect to */
    rv = FindDaemon(ss, &daemon);
    if (rv) {
	return -1;
    }
    direct = ChooseAddress(ss, (struct sockaddr_in*)sa);
    if (direct) {
	sip = (struct sockaddr_in*) sa;
	ss->socks->direct = 1;
    } else {
	sip = &daemon;
	ss->socks->direct = 0;
    }
    SSL_TRC(10, ("%d: SSL[%d]: socks %s connect to %d.%d.%d.%d:%d",
		 SSL_GETPID(), ss->fd,
		 direct ? "direct" : "sockd",
		 (ntohl(sip->sin_addr.s_addr) >> 24) & 0xff,
		 (ntohl(sip->sin_addr.s_addr) >> 16) & 0xff,
		 (ntohl(sip->sin_addr.s_addr) >> 8) & 0xff,
		 ntohl(sip->sin_addr.s_addr) & 0xff,
		 ntohs(sip->sin_port)));

    /* Attempt first connection */
    rv = XP_SOCK_CONNECT(ss->fd, (struct sockaddr*)sip, sizeof(*sip));
    if (rv < 0) {
	err = XP_SOCK_ERRNO;
	PORT_SetError(err);
	if (err != XP_ERRNO_EISCONN) {
	    return rv;
	}
	/* Async connect finished */
    }

    /* If talking to sockd, do handshake */
    if (!direct) {
	/* Find user */
#ifdef XP_UNIX
#ifdef __386BSD__
	user = bsdi_cuserid(0);
#else
	user = cuserid(0);
#endif
	if (!user) {
	    PORT_SetError(XP_ERRNO_EINVAL);
	    SSL_DBG(("%d: SSL[%d]: cuserid fails, errno=%d",
		     SSL_GETPID(), ss->fd, PORT_GetError()));
	    return -1;
	}
#else
	user = "SSL";
#endif

	/* Send our message to it */
	rv = SayHello(ss, SOCKS_CONNECT, (struct sockaddr_in*) sa, user);
	if (rv) {
	    return rv;
	}

	ss->handshake = StartGather;
	ss->nextHandshake = 0;

	/* save up who we're really talking to so we can index the cache */
	ss->peer = ((struct sockaddr_in *)sa)->sin_addr.s_addr;
	ss->port = ((struct sockaddr_in *)sa)->sin_port;
    }
    return 0;
}

static int WaitForResponse(SSLSocket *ss)
{
    int rv;

    ss->handshake = StartGather;
    ss->nextHandshake = 0;

    /* Get response. Do it now, spinning if necessary (!) */
    for (;;) {
	rv = ssl_ReadHandshake(ss);
	if (rv == -2) {
#ifdef XP_UNIX
	    /*
	    ** Spinning is really evil under unix. Call select and
	    ** continue when a read select returns true. We only get
	    ** here if the socket was marked async before the bind
	    ** call.
	    */
	    fd_set spin;
	    PORT_Memset(&spin, 0, sizeof(spin));
	    FD_SET(ss->fd, &spin);
	    rv = select(ss->fd+1, &spin, 0, 0, 0);
	    if (rv < 0) {
		return rv;
	    }
	    if (rv == 0) {
		/* Buggy OS */
		PORT_SetError(XP_ERRNO_EIO);
		return -1;
	    }
#endif
	    continue;
	}
	break;
    }
    return rv;
}

int ssl_SocksBind(SSLSocket *ss, const void *sa, int salen)
{
    SSLSocksInfo *si;
    int rv, direct;
    struct sockaddr_in daemon;
    char *user;

    PORT_Assert(ss->socks != 0);
    si = ss->socks;

    /* Figure out where to connect to */
    rv = FindDaemon(ss, &daemon);
    if (rv) {
	return -1;
    }
    direct = ChooseAddress(ss, (struct sockaddr_in*)sa);
    if (direct) {
	ss->socks->direct = 1;
	rv = XP_SOCK_BIND(ss->fd, (struct sockaddr*) sa, salen);
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	}
	PORT_Memcpy(&ss->socks->bindAddr, sa, sizeof(struct sockaddr_in));
    } else {
	ss->socks->direct = 0;
	SSL_TRC(10, ("%d: SSL[%d]: socks sockd bind to %d.%d.%d.%d:%d",
		     SSL_GETPID(), ss->fd,
		     (ntohl(daemon.sin_addr.s_addr) >> 24) & 0xff,
		     (ntohl(daemon.sin_addr.s_addr) >> 16) & 0xff,
		     (ntohl(daemon.sin_addr.s_addr) >> 8) & 0xff,
		     ntohl(daemon.sin_addr.s_addr) & 0xff,
		     ntohs(daemon.sin_port)));

	/* First connect to socks daemon. ASYNC connects must be disabled! */
	rv = XP_SOCK_CONNECT(ss->fd, (struct sockaddr*)&daemon, sizeof(daemon));
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	    return rv;
	}

	/* Find user */
#ifdef XP_UNIX
#ifdef __386BSD__
	user = bsdi_cuserid(0);
#else
	user = cuserid(0);
#endif
	if (!user) {
	    SSL_DBG(("%d: SSL[%d]: cuserid fails, errno=%d",
		     SSL_GETPID(), ss->fd, PORT_GetError()));
	    PORT_SetError(XP_ERRNO_EINVAL);
	    return -1;
	}
#else
	user = "SSL";
#endif
	/* Send message to sockd */
	rv = SayHello(ss, SOCKS_BIND, (struct sockaddr_in*) sa, user);
	if (rv) {
	    return rv;
	}

	/* Gather up bind response from sockd */
	rv = WaitForResponse(ss);
	if (rv == 0) {
	    /* Done */
	    si->bindAddr.sin_family = AF_INET;
	    si->bindAddr.sin_port = si->destPort;
	    if (ntohl(si->destHost) == INADDR_ANY) {
		si->bindAddr.sin_addr.s_addr = daemon.sin_addr.s_addr;
	    } else {
		si->bindAddr.sin_addr.s_addr = si->destHost;
	    }
	}
    }
    si->didBind = 1;
    return rv;
}

int
SSL_BindForSockd(int s, const void *sa, int salen, long dsthost)
{
    SSLSocket *ss;
    SSLSocksInfo *si;
    int rv, direct;
    struct sockaddr_in daemon;
    char *user;
    struct sockaddr_in dst;

    dst.sin_family = AF_INET;
    dst.sin_addr.s_addr = dsthost;
    dst.sin_port = 0;

    ss = ssl_FindSocket(s);
    if (!ss) {
	SSL_DBG(("%d: SSL[%d]: bad socket in bind", SSL_GETPID(), s));
	return -1;
    }

    si = ss->socks;
    if (si == NULL) {
	SSL_DBG(("%d: SSL[%d]: no socks information in bind", SSL_GETPID(), s));
	return -1;
    }

    /* Figure out where to connect to */
    rv = FindDaemon(ss, &daemon);
    if (rv) {
	return -1;
    }
    direct = ChooseAddress(ss, &dst);
    if (direct) {
        ss->socks->direct = 1;
        rv = XP_SOCK_BIND(ss->fd, (struct sockaddr*) sa, salen);
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	}
        PORT_Memcpy(&ss->socks->bindAddr, sa, sizeof(struct sockaddr_in));
    } else {
        ss->socks->direct = 0;

        /* First connect to socks daemon. ASYNC connects must be disabled! */
        rv = XP_SOCK_CONNECT(ss->fd, (struct sockaddr*)&daemon, sizeof(daemon));
        if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
            return rv;
        }

        /* Find user */
#ifdef XP_UNIX
#ifdef __386BSD__
        user = bsdi_cuserid(0);
#else
        user = cuserid(0);
#endif
        if (!user) {
	    SSL_DBG(("%d: SSL[%d]: cuserid fails, errno=%d",
		     SSL_GETPID(), ss->fd, PORT_GetError()));
	    PORT_SetError(XP_ERRNO_EINVAL);
            return -1;
	}
#else
	user = "SSL";
#endif
        /* Send message to sockd */
        rv = SayHello(ss, SOCKS_BIND, &dst, user);
        if (rv) {
            return rv;
        }

        /* Gather up bind response from sockd */
        rv = WaitForResponse(ss);

        if (rv == 0) {
            /* Done */
            si->bindAddr.sin_family = AF_INET;
            si->bindAddr.sin_port = si->destPort;
            if (ntohl(si->destHost) == INADDR_ANY) {
                si->bindAddr.sin_addr.s_addr = daemon.sin_addr.s_addr;
            } else {
                si->bindAddr.sin_addr.s_addr = si->destHost;
            }
        }
    }
    si->didBind = 1;
    return rv;
}

#if 0
static int SocksSend(SSLSocket *ss, unsigned char* bp, int len, int flags)
{
    int rv = XP_SOCK_SEND(ss->fd, bp, len, flags);
    if (rv < 0) {
	PORT_SetError(XP_SOCK_ERRNO);
    }
    return rv;
}
#endif

int ssl_SocksAccept(SSLSocket *ss, void *addr, int *addrlenp)
{
    SSLSocket *ns;
    SSLSocksInfo *si;
    int rv;

    PORT_Assert(ss->socks != 0);
    si = ss->socks;

    if (*addrlenp < sizeof(struct sockaddr_in)) {
	PORT_SetError(XP_ERRNO_EINVAL);
	return -1;
    }

    if (!si->didBind || si->direct) {
	/*
	** If we didn't do the bind yet this call will generate an error
	** from the OS. If we did do the bind then we must be direct and
	** let the OS do the accept.
	*/
	rv = (*ssl_accept_func)(ss->fd, (struct sockaddr*) addr, addrlenp);
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	}
	return rv;
    }

    /* Get next accept response from server */
    rv = WaitForResponse(ss);
    if (rv) {
	return rv;
    }

    /* Handshake finished. Give dest address back to caller */
    ((struct sockaddr_in*)addr)->sin_family = AF_INET;
    ((struct sockaddr_in*)addr)->sin_port = si->destPort;
    ((struct sockaddr_in*)addr)->sin_addr.s_addr = si->destHost;

    /* Dup the descriptor and return it */
    rv = XP_SOCK_DUP(ss->fd);
    if (rv < 0) {
	PORT_SetError(XP_SOCK_ERRNO);
	return rv;
    }

    /* Dup the socket structure */
    ns = ssl_DupSocket(ss, rv);
    if (!ns) {
	XP_SOCK_CLOSE(rv);
	return -1;
    }

    return rv;
}

int ssl_SocksListen(SSLSocket *ss, int backlog)
{
    int rv;

    PORT_Assert(ss->socks != 0);

    if (ss->socks->direct) {
	rv = XP_SOCK_LISTEN(ss->fd, backlog);
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	}
	return rv;
    }
    return 0;
}

int ssl_SocksGetsockname(SSLSocket *ss, void *name, int *namelenp)
{
    int rv;

    PORT_Assert(ss->socks != 0);
    if (!ss->socks->didBind || ss->socks->direct) {
	rv = XP_SOCK_GETSOCKNAME(ss->fd, (struct sockaddr *)name, namelenp);
	if (rv < 0) {
	    PORT_SetError(XP_SOCK_ERRNO);
	}
	return rv;
    }

    if (*namelenp < sizeof(struct sockaddr_in)) {
	PORT_SetError(XP_ERRNO_EINVAL);
	return -1;
    }
    *namelenp = sizeof(struct sockaddr_in);
    PORT_Memcpy(name, &ss->socks->bindAddr, sizeof(struct sockaddr_in));
    return 0;
}

int ssl_SocksRecv(SSLSocket *ss, void *buf, int len, int flags)
{
    int rv;

    PORT_Assert(ss->socks != 0);

    if (ss->handshake) {
	rv = ssl_ReadHandshake(ss);
	if (rv < 0) {
	    return rv;
	}
	if (ssl_SendSavedWriteData(ss, &ss->saveBuf, ssl_DefSend) < 0) {
	    return -1;
	}
    }

    rv = XP_SOCK_RECV(ss->fd, buf, len, flags);
    SSL_TRC(2, ("%d: SSL[%d]: recving %d bytes from sockd",
		SSL_GETPID(), ss->fd, rv));
    if (rv < 0) {
	PORT_SetError(XP_SOCK_ERRNO);
    }
    return rv;
}

int ssl_SocksRead(SSLSocket *ss, void *buf, int len)
{
    return ssl_SocksRecv(ss, buf, len, 0);
}

int ssl_SocksSend(SSLSocket *ss, const void *buf, int len, int flags)
{
    int rv;

    PORT_Assert(ss->socks != 0);

    if (len == 0) return 0;
    if (ss->handshake) {
	rv = ssl_WriteHandshake(ss, buf, len);
	if (rv < 0) {
	    if (rv == -2) {
		return len;
	    }
	    return rv;
	}
	if (ssl_SendSavedWriteData(ss, &ss->saveBuf, ssl_DefSend) < 0) {
	    return -1;
	}
    }

    SSL_TRC(2, ("%d: SSL[%d]: sending %d bytes using socks",
		SSL_GETPID(), ss->fd, len));

    /* Send out the data */
    rv = ssl_DefSend(ss, (unsigned char*)buf, len, flags);
    return rv;
}

int ssl_SocksWrite(SSLSocket *ss, const void *buf, int len)
{
    return ssl_SocksSend(ss, buf, len, 0);
}

int SSL_CheckDirectSock(int s)
{
    SSLSocket *ss;

    ss = ssl_FindSocket(s);
    if (!ss) {
	SSL_DBG(("%d: SSL[%d]: bad socket in CheckDirectSock", SSL_GETPID(), s));
	return -1;
    }

    if (ss->socks != NULL) {
	return ss->socks->direct;
    }
    return -1;
}


int SSL_ConfigSockd(int s, unsigned long host, short port)
{
    SSLSocket *ss;
    int rv;

    ss = ssl_FindSocket(s);
    if (!ss) {
	SSL_DBG(("%d: SSL[%d]: bad socket in ConfigSocks", SSL_GETPID(), s));
	return -1;
    }

    /* Create socks info if not already done */
    rv = ssl_CreateSocksInfo(ss);
    if (rv) {
	return rv;
    }
    ss->socks->sockdHost = host;
    ss->socks->sockdPort = port;
    return 0;
}