/* Copyright (c) 1995,1996,1997 NEC Corporation.  All rights reserved.       */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: wrap_tcp.c,v 1.63.2.4.2.4 1998/06/29 22:43:04 salman Exp $
 */

#define HIDEORIG
#include "socks5p.h"
#include "buffer.h"
#include "block.h"
#include "addr.h"
#include "wrap.h"
#include "protocol.h"
#include "libproto.h"
#include "cache.h"
#include "log.h"
#include "msg.h"

#ifdef EALREADY
#define INPROGRESS_ERRNO EALREADY
#else
#define INPROGRESS_ERRNO EINPROGRESS
#endif

#ifndef HAVE_RRESVPORT
S5IOHandle REAL(rresvport)(int *portp) {
    S5IOHandle sd;
    S5NetAddr na;

    memset((char *)&na, 0, sizeof(S5NetAddr));
#ifdef HAVE_NETINET6_IN6_H
    na.sin6.sin_family     = AF_INET6;
#else
    na.sin.sin_family      = AF_INET;
    na.sin.sin_addr.s_addr = INADDR_ANY;
#endif

    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Rresvport: socket failed: %m");
	return sd;
    }

    if (*portp >= IPPORT_RESERVED || *portp < IPPORT_RESERVED/2) {
	*portp = IPPORT_RESERVED-1;
    }

    for (; *portp >= IPPORT_RESERVED/2; (*portp)--) {
	lsAddrSetPort(&na, htons((u_short)*portp));

	if (REAL(bind)(sd, &na.sa, lsAddrSize(&na)) >= 0) {
	    return sd;
	} else if (!ISSOCKETERROR(EADDRINUSE)) {
	    CLOSESOCKET(sd);
	    return S5InvalidIOHandle;
	}
    }

    CLOSESOCKET(sd);
    SETSOCKETERROR(EAGAIN);
    return S5InvalidIOHandle;
}

#endif

/* Find out if connection to proxy has been established, if it was left in   */
/* the connecting state...(non-blocking).  If there's anything to read       */
/* there, we'll read it... (does nonblocking IO really work this way?  I     */
/* think not, I think connect twice fails). Anyhow, if it worked, mark the   */
/* connection as most recent and return success.                             */
static int lsTcpFinishNBConnect(S5IOHandle sd, lsSocksInfo *pcon) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpFinishNBConnect: connection was in progress");

    switch (S5IOCheck(sd)) {
	case 0:
	    /* If the socket is now blocking, we should wait for things to   */
	    /* finish.  This would be kind of weird, but it could happen,    */
	    /* especially if read or write is the calling function.          */
	    /*                                                               */
	    /* 8/28/97 - blob                                                */
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpFinishNBConnect: connection in progress - no data");
	    SETSOCKETERROR(EAGAIN);
	    return -1;
	case -1:
	    if (!ISSOCKETERROR(EINTR)) {
		lsConnectionDel(sd);
		SETSOCKETERROR(ECONNREFUSED);
	    }
	    return -1;
    }
		
    if (!lsLibReadResponse(pcon)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpFinishNBConnect: connection completed");
	pcon->status = CON_ESTABLISHED;
	lsLastCon = pcon;
	return 0;
    } else {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpFinsihNBConnect: connection failed");
	lsConnectionDel(sd);
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }
}

/* wrapper around the connect system call.                                   */
/* returns 0 on success; -1 on failure                                       */
int lsTcpConnect(S5IOHandle sd, CONST ss *name, int namelen) {
    lsProxyInfo *pri;
    lsSocksInfo *pcon;
    int rval;
    
    if (!(pcon = lsConnectionFind(sd))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: No connection found");
    } else if (pcon->cmd == SOCKS_CONNECT) {
	if (!(pri = pcon->pri) || pri->how == DIRECT) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: Prior direct connection found...");
	    return REAL(connect)(sd, (ss *)name, namelen);
	}
	
	switch (pcon->status) {
	    case CON_ESTABLISHED:
		if (S5IOCheck(sd) >= 0) {
		    SETSOCKETERROR(EISCONN);
		    return -1;
		}
	    case CON_INPROGRESS:
		/* When a non-blocking connect finally goes through, return  */
		/* EISCONN.  If it does not go through and the finish part   */
		/* return EAGAIN, return EINPROGRESS or EALREADY(???).       */
		if ((rval = lsTcpFinishNBConnect(sd, pcon)) < 0 && ISSOCKETERROR(EAGAIN)) SETSOCKETERROR(INPROGRESS_ERRNO);
		return rval;
	    case CON_NOTESTABLISHED:
		/* XXX I am not sure quite what the right thing to do here   */
		/* is.  Several things come to mind...                       */
		/*                                                           */
		/* - We could have missed a close, and this could be a new,  */
		/*   valid socket. If that's the case, we should probably    */
		/*   handle it (?)                                           */
		/* - On some OSs, a second connect will try to connect       */
		/*   again.  Should we handle that behavior?  I think not.   */
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: non blocking connect failed sometime");
		SETSOCKETERROR(pcon->serrno?pcon->serrno:ECONNREFUSED);
		lsConnectionDel(sd);
		return -1;
	    default:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: unknown status: %d", pcon->status);
		if (S5IOCheck(sd) >= 0) REAL(shutdown)(sd, 2);
		lsConnectionDel(sd);
		SETSOCKETERROR(EBADF);
		return -1;
	}
    } else if (pcon->cmd == SOCKS_BIND) {
	switch (pcon->status) {
	    case CON_NOTESTABLISHED:
                REAL(bind)(sd, &pcon->peer.sa, lsAddrSize(&pcon->peer));
		lsConnectionDel(sd);
		break;
            case CON_BOUND:
                /* We bound, assumed it was an Rbind, but it wasn't.  We     */
                /* need to close that socket, make a new one, and bind it to */
                /* the same address as the old one (since we may have        */
                /* already told someone the address we bound to).            */
                {
                    S5NetAddr sa;
                    int ssasize = sizeof(sa);
                    S5IOHandle nsd;

                    memset(&sa, 0, sizeof(sa));
                    sa.sa.sa_family = AF_INET;
                    if (REAL(getsockname)(sd, (ss *)&sa, &ssasize) < 0) {
                        SETSOCKETERROR(EBADF);
                        return -1;
                    }

                    if ((nsd = socket(AF_INET, SOCK_STREAM, 0)) == S5InvalidIOHandle) {
                        SETSOCKETERROR(EBADF);
                        return -1;
                    }

                    lsConnectionDel(sd);
 
                    if (REAL(dup2)(nsd, sd) == S5InvalidIOHandle) {
                        SETSOCKETERROR(EBADF);
                        return -1;
                    }

                    CLOSESOCKET(nsd);
 
                    if (REAL(bind)(sd, (ss *)&sa, ssasize) < 0) {
                        SETSOCKETERROR(EBADF);
                        return -1;
                    }

                    break;
                }
	    default:
		SETSOCKETERROR(EADDRINUSE);
		return -1;
	}
    }	

    if ((pcon = lsLibProtoExchg(sd, (S5NetAddr *)name, SOCKS_CONNECT)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: Protocol exchange failed");
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpConnect: Protocol exchanged");
    
    if (pcon->status == CON_INPROGRESS) {
	SETSOCKETERROR(EINPROGRESS);
	return -1;
    }
    
    pcon->status = CON_ESTABLISHED;
    if ((lsLastCon = pcon)->pri && pcon->pri->how != DIRECT) return 0;
    return REAL(connect)(sd, (ss *)name, namelen);
}

static int proxy_bind(int sd, const S5NetAddr *peer, lsSocksInfo **p) {
    lsSocksInfo *pcon;
    
    if ((pcon = lsLibProtoExchg(sd, peer, SOCKS_BIND)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpBind: Protocol exchange failed");	
	return -1;
    }
    
    /* XXX might need to fix some stuff here...                              */
    /* Specifically, pcon->pri->pxry may be wrong                            */
    if (p) *p = pcon;
    return 0;
}

/* wrapper around the bind system call.                                      */
/* returns 0 on success; -1 on failure                                       */
/* if the client is asking to be bound to a specific port and address, then  */
/* probably we should bind to that address.  Here since we are not actually  */
/* binding, but asking the proxy server to bind, maybe we should ask the     */
/* proxy server to bind to this specific port. XXX first bind locally (this  */
/* is not needed in strict sense) XXX                                        */
int lsTcpBind(S5IOHandle sd, CONST ss *name, int namelen) {
    lsSocksInfo *pcon;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpBind: fd %d", sd);

    if ((pcon = lsConnectionFind(sd)) && pcon->status != CON_NOTESTABLISHED) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpBind: Prior connection found");
 	SETSOCKETERROR(EINVAL);
	return -1;
    }

    if (!pcon && !(pcon = lsLibProtoExchg(sd, NULL, SOCKS_BIND))) {
	SETSOCKETERROR(EBADF);
	return -1;
    }

    lsAddrCopy(&pcon->peer, (S5NetAddr *)name, lsAddrSize((S5NetAddr *)name));

    return 0;
}

static int lsTcpFinishBind(S5IOHandle sd, lsSocksInfo *pcon) {
    S5NetAddr rbind, nmcpy;
    u_short port;

    if (!lsLastCon || !lsLastCon->pri || lsLastCon->pri->how == DIRECT) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsTcpFinishBind: No prior connection found, binding locally");
	lsConnectionDel(sd);
        return REAL(bind)(sd, &pcon->peer.sa, lsAddrSize(&pcon->peer));
    }

    if (lsAddr2Port(&pcon->peer) != (u_short)0 && lsAddrIsNull(&pcon->peer) && getenv("SOCKS5_BINDLOCALLYALSO")) {
#define ISLASTCONOUTINTFC(x) (lsLastCon && lsLastCon->pri && lsLastCon->pri->how != DIRECT && !lsAddrAddrComp((x), &lsLastCon->pri->prxyout))
        lsAddrCopy(&nmcpy, &pcon->peer, lsAddrSize(&pcon->peer));

        /* In order to make the following local bind work, make sure we      */
        /* aren't trying to bind to the socks server's address... It should  */
        /* fail, but on linux it sometimes succeeds (if root).               */
        if (ISLASTCONOUTINTFC(&nmcpy)) {
            switch (nmcpy.sa.sa_family) {
                case AF_INET:
                    nmcpy.sin.sin_addr.s_addr = INADDR_ANY;
                    break;
            }
        }

        /* try to bind locally first (this may not be strictly needed) XXX   */
        REAL(bind)(sd, &nmcpy.sa, lsAddrSize(&nmcpy));
    }

    /* We want to bind to the port they ask for in bind, but on the          */
    /* server...  We might as well try to bind to it here also...  Also, we  */
    /* don't care what address we use to reach the server (and the           */
    /* application will probably ahve it wrong), so make it INADDR_ANY.      */
    lsAddrCopy(&rbind, &lsLastCon->peer, lsAddrSize(&lsLastCon->peer));

    /* To follow the spec, we can't change the port number...                */
    /* lsAddrSetPort(&rbind, lsAddr2Port((S5NetAddr *)name));                */

    if (proxy_bind(sd, &rbind, NULL) < 0) {
	SETSOCKETERROR(EADDRNOTAVAIL);
 	return -1;
    }

    /* Although the returned port should be OK, the returned address might   */
    /* 0. If this is the case, we are going to use the last TCP connection's */
    /* outbound address as the binded address...                             */
    if (pcon->pri && pcon->pri->how != DIRECT) {
	port = lsAddr2Port(&pcon->pri->prxyout);
	if (!lsAddrIsNull(&pcon->pri->prxyout)) {
	    if (!lsAddrIsNull(&lsLastCon->pri->prxyout)) {
	        lsAddrCopy(&pcon->pri->prxyout, &pcon->pri->prxyin, lsAddrSize(&pcon->pri->prxyin));
            } else {
		lsAddrCopy(&pcon->pri->prxyout, &lsLastCon->pri->prxyout, lsAddrSize(&lsLastCon->pri->prxyout));
	    }

	    lsAddrSetPort(&pcon->pri->prxyout, port);
        }
    }

    pcon->status = CON_BOUND;
    return 0;
}

/* wrapper around the rresvport system call.                                 */
/* first bind locally we always have to call this whether expecting          */
/* connections directly or through the proxy server XXX                      */
/* returns 0 on success; -1 on failure                                       */
S5IOHandle LIBPREFIX(rresvport)(int *port) {
    S5NetAddr rbind;
    lsSocksInfo *pcon = NULL;
    S5IOHandle sd;
    
    LIBPREFIX2(init)("libsocks5");

    if (!lsLastCon || !lsLastCon->pri || lsLastCon->pri->how == DIRECT) return REAL(rresvport)(port);

    if ((sd = REAL(rresvport)(port)) == S5InvalidIOHandle) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Could not reserve local port");
	return S5InvalidIOHandle;
    }

    lsAddrCopy(&rbind, &lsLastCon->peer, lsAddrSize(&lsLastCon->peer));
    lsAddrSetPort(&rbind, (u_short)*port);

    if (proxy_bind(sd, &lsLastCon->peer, &pcon) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Could not bind on proxy");

	if (pcon) lsConnectionDel(sd);
	SETSOCKETERROR(EADDRINUSE);
	return S5InvalidIOHandle;
    }

    if (pcon) *port = pcon->pri->prxyout.sin.sin_port;
    return sd;
}

/* wrapper around the listen system call. returns 0 on succ; -1 on failure   */
/* if the connection is through the proxy and it is SOCKS_BIND, then the     */
/* proxy is already listening on our behalf, so simply return...otherwise,   */
/* we really want to listen, so do it...                                     */             
int LIBPREFIX(listen)(S5IOHandle sd, int backlog) {
    lsSocksInfo *pcon;
    
    LIBPREFIX2(init)("libsocks5");
    if ((pcon = lsConnectionFind(sd)) && pcon->cmd == SOCKS_BIND) {
	if (pcon->status == CON_NOTESTABLISHED && lsTcpFinishBind(sd, pcon) < 0) {
	    SETSOCKETERROR(EBADF);
	    return -1;
        }

	if (!pcon->pri || pcon->pri->how == DIRECT) return REAL(listen)(sd, backlog);
	return 0;
    }
	
    return REAL(listen)(sd, backlog);
}

/* wrapper around getsockname system call. returns 0 on succ; -1 on failure  */
/* if the connection is through the proxy and it is SOCKS_BIND, then the     */
/* proxy is listening on our behalf, so return the port/address on which the */
/* proxy is listening on...so that someone else can connect to it...         */
int lsTcpGetsockname(S5IOHandle sd, ss *name, int *namelen) {
    lsSocksInfo *pcon;
    
    if (!(pcon = lsConnectionFind(sd))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS getsockname: No valid connection");
	return REAL(getsockname)(sd, name, namelen);
    }

    if (pcon->cmd == SOCKS_UDP) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS getsockname: Wrong command type");
	return REAL(getsockname)(sd, name, namelen);	
    }

    if (pcon->cmd == SOCKS_BIND && pcon->status == CON_NOTESTABLISHED) {
	if (lsTcpFinishBind(sd, pcon) < 0) {
	    SETSOCKETERROR(EBADF);
	    return -1;
	}
    }

    if (!pcon->pri || pcon->pri->how == DIRECT) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS getsockname: Direct");
	return REAL(getsockname)(sd, name, namelen);	
    }

    if (name) {
    	*namelen = MIN(*namelen, lsAddrSize(&pcon->pri->prxyout));
        lsAddrCopy((S5NetAddr *)name, &pcon->pri->prxyout, *namelen);
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS getsockname (fd: %d) %s:%d", sd, ADDRANDPORT(&pcon->pri->prxyout));

    return 0;
}

/* wrapper around getpeername system call. returns 0 on succ; -1 on failure  */
/* if the connection is through the proxy and it is SOCKS_BIND, then the     */
/* proxy connected on our behalf, so return the port/address of the person   */
/* who is connected to other end of the proxy...                             */
int lsTcpGetpeername(S5IOHandle sd, ss *name, int *namelen) {
    lsSocksInfo *pcon;

    if (!(pcon = lsConnectionFind(sd)) && (pcon->pri && pcon->pri->how == DIRECT)) {
	return REAL(getpeername)(sd, name, namelen);
    }

    /* Some people call getpeer name to see if nb-connects succeed, so it    */
    /* should finish the connect if possible.                                */
    if (pcon->cmd == SOCKS_CONNECT && pcon->status == CON_INPROGRESS) {
	if (lsTcpFinishNBConnect(sd, pcon) < 0) {
	    if (ISSOCKETERROR(EAGAIN)) SETSOCKETERROR(ENOTCONN);
	    else SETSOCKETERROR(EBADF);
            return -1;
        }
    }

    if (name) {
    	*namelen = MIN(*namelen, lsAddrSize(&pcon->peer));
        lsAddrCopy((S5NetAddr *)name, &pcon->peer, *namelen);
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Getpeername (fd: %d) %s:%d", sd, ADDRANDPORT(&pcon->peer));

    return 0;
}

/* wrapper around accept system call. returns 0 on success; -1 on failure    */
/* Here we accept locally if something went wrong, like this socket hasn't   */
/* seen a bind yet.  If we do not do that, we perform the accept protocol    */
/* with the server (recv a message), and then dup the file descriptor (***)  */
/* because clients usually close the acceptingfd and leave open the          */
/* accepted one.  Once we've done that, we add the connection, and return.   */
S5IOHandle LIBPREFIX(accept)(S5IOHandle sd, ss *name, int *namelen) {
    extern S5IOHandle LIBPREFIX(dup) P((S5IOHandle));
    S5IOHandle fd = S5InvalidIOHandle;
    lsSocksInfo *pcon, *ncon;

    LIBPREFIX2(init)("libsocks5");

    if (!(pcon = lsConnectionFind(sd))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS accept: this socket wasn't registered... ");
	return REAL(accept)(sd, name, namelen);
    }

    if (pcon->cmd != SOCKS_BIND) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS accept: this socket wasn't bound...");
	SETSOCKETERROR(EBADF);
	return S5InvalidIOHandle;
    }
    
    if (!pcon->pri || pcon->pri->how == DIRECT) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS accept: this socket was bound locally...");
	return REAL(accept)(sd, name, namelen);
    }
    
    if (pcon->status == CON_NOTESTABLISHED) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS accept: this socket wasn't accepting...");
	lsConnectionDel(sd);
	SETSOCKETERROR(EBADF);
	return S5InvalidIOHandle;
    }

    if (ISNBLOCK(sd)) {
	struct timeval tv = { 0, 0 };
	fd_set fds;
	
	FD_ZERO(&fds);
	FD_SET(sd, &fds);
	
	switch (REAL(select)(sd+1, &fds, NULL, NULL, &tv)) {
	    case -1:
		if (!ISSOCKETERROR(EINTR)) SETSOCKETERROR(EBADF);
		return S5InvalidIOHandle;
	    case 0:
		SETSOCKETERROR(EWOULDBLOCK);
		return S5InvalidIOHandle;
	}
    }
	    
    if ((fd = LIBPREFIX(dup)(sd)) == S5InvalidIOHandle) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Dup error when accepting on Proxy Server: %m");
	return S5InvalidIOHandle;
    }

    if ((ncon = lsConnectionFind(fd)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Dup didn't copy library info?");
	REAL(close)(fd);
	SETSOCKETERROR(EBADF);
	return S5InvalidIOHandle;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Accepting on Proxy Server");
    ncon->status = CON_ACCEPTING;

    if (lsLibReadResponse(ncon) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Protocol error when accepting on Proxy Server");
	REAL(close)(fd);
	lsConnectionDel(fd);
	SETSOCKETERROR(EBADF);
	return S5InvalidIOHandle;
    }

    if (name) { 
    	*namelen = MIN(*namelen, lsAddrSize(&ncon->peer));
        lsAddrCopy((S5NetAddr *)name, &ncon->peer, *namelen); 
    } 

    ncon->status = CON_ESTABLISHED;
    return fd;
}

IORETTYPE lsTcpSend(S5IOHandle sd, const IOPTRTYPE msg, IOLENTYPE len, int flags) {
    lsSocksInfo *pcon = lsConnectionFind(sd);

    if (pcon == NULL) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSend: Direct");
	return REAL(send)(sd, msg, len, flags);
    }

    if (pcon->cmd == SOCKS_CONNECT && pcon->status == CON_INPROGRESS) {
        if (lsTcpFinishNBConnect(sd, pcon) < 0) {
            if (!ISSOCKETERROR(EAGAIN) && !ISSOCKETERROR(EINTR)) SETSOCKETERROR(EPIPE);
#if defined(sun) && !defined(__srv4__)
            else if (ISSOCKETERROR(EAGAIN)) SETSOCKETERROR(EWOULDBLOCK);
#endif
            return -1;
        }
    } else if (pcon->status != CON_ESTABLISHED) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSend: Nonblocking connect error: %m");
        SETSOCKETERROR(EBADF);
	return -1;
    }

    if (pcon->pri && pcon->pri->cinfo.auth.encode) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSend: Encapsulated");
        return S5BufWritePacket(sd, &pcon->pri->cinfo, (char *)msg, len, flags);
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSend: Default");
    return REAL(send)(sd, msg, len, flags);
}

IORETTYPE lsTcpRecv(S5IOHandle sd, IOPTRTYPE msg, IOLENTYPE len, int flags) {
    lsSocksInfo *pcon = lsConnectionFind(sd);

    if (pcon == NULL) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpRecv: Direct");
	return REAL(recv)(sd, msg, len, flags);
    }

    if (pcon->cmd == SOCKS_CONNECT && pcon->status == CON_INPROGRESS) {
	if (lsTcpFinishNBConnect(sd, pcon) < 0) {
	    if (!ISSOCKETERROR(EAGAIN) && !ISSOCKETERROR(EINTR)) return 0;
#if defined(sun) && !defined(__srv4__)
            else if (ISSOCKETERROR(EAGAIN)) SETSOCKETERROR(EWOULDBLOCK);
#endif
            return -1;
        }
    } else if (pcon->status != CON_ESTABLISHED) {
	SETSOCKETERROR(pcon->serrno);
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpRecv: Nonblocking connect error: %m");
        SETSOCKETERROR(EBADF);
        return -1;
    }

    if (pcon->pri && pcon->pri->cinfo.auth.encode) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpRecv: Encapsulated");
        return S5BufReadPacket(sd, &pcon->pri->cinfo, (char *)msg, len, flags);
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpRecv: Default");
    return REAL(recv)(sd, msg, len, flags);
}

IORETTYPE lsTcpRecvfrom(S5IOHandle sd, IOPTRTYPE msg, IOLENTYPE len, int flags, ss *from, int *fromlen) {
    lsSocksInfo *pcon = lsConnectionFind(sd);

    if (!pcon || (pcon->pri && pcon->pri->how == DIRECT)) {
        return REAL(recvfrom)(sd, msg, len, flags, from, fromlen);
    }

    if (from) {
        *fromlen = MIN(*fromlen, lsAddrSize(&pcon->peer));
        lsAddrCopy((S5NetAddr *)from, &pcon->peer, *fromlen);
    }

    return lsTcpRecv(sd, msg, len, flags);
}

IORETTYPE
lsTcpRecvmsg(S5IOHandle sd, ms *msg, int flags)
{
char *buf;
int  rc, i, len=0;
 
    lsSocksInfo *pcon = lsConnectionFind(sd);
 
    if(!pcon || !pcon->pri || pcon->pri->how == DIRECT) {
     return REAL(recvmsg)(sd, msg, flags);
    }
 
    if(msg->msg_name) {
     msg->msg_namelen = MIN(msg->msg_namelen, lsAddrSize(&pcon->peer));
     lsAddrCopy((S5NetAddr *)msg->msg_name, &pcon->peer, msg->msg_namelen);
    }
 
    for(i=0; i<msg->msg_iovlen; i++)
     len = len + msg->msg_iov[i].iov_len;
 
    buf= (char *) malloc(len);
 
    if ((rc = lsTcpRecv(sd, (void *)buf, len, flags)) < 0)
     return -1;
 
    for(i=0; i<msg->msg_iovlen; i++) {
     memcpy(msg->msg_iov[i].iov_base,buf,msg->msg_iov[i].iov_len);
     buf = buf + msg->msg_iov[i].iov_len;
    }
 
    if(buf)
     free(buf);
    return rc;
}

IORETTYPE
lsTcpSendmsg(S5IOHandle sd, ms *msg, int flags)
{
char *buf;
int  len=0;
int  i;
lsSocksInfo *pcon = lsConnectionFind(sd);
 
    if(pcon == NULL) {
     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSendmsg: Direct");
     return REAL(sendmsg)(sd, msg, flags);
    }
 
    if(pcon->cmd == SOCKS_CONNECT && pcon->status == CON_INPROGRESS) {
     if (lsTcpFinishNBConnect(sd, pcon) < 0) {
      if(!ISSOCKETERROR(EAGAIN) && !ISSOCKETERROR(EINTR))
       SETSOCKETERROR(EPIPE);
#if defined(sun) && !defined(__srv4__)
      else
      if(ISSOCKETERROR(EAGAIN))
	   SETSOCKETERROR(EWOULDBLOCK);
#endif
	 return -1;
	 }
    } 	/* if(pcon->cmd == SOCKS_CONNECT && pcon->status == CON_INPROGRESS) */
 
    else
    if(pcon->status != CON_ESTABLISHED) {
     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0,\
					"TcpSendmsg: Nonblocking connect error: %m");
     SETSOCKETERROR(EBADF);
     return -1;
    }
 
    if (pcon->pri && pcon->pri->cinfo.auth.encode) {
     for(i=0; i<msg->msg_iovlen; i++)
      len += msg->msg_iov[i].iov_len;

	 buf = (char *) malloc(len);
 
     for(i=0; i<msg->msg_iovlen; i++) {
      memcpy(buf, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len);
      buf = buf + msg->msg_iov[i].iov_len;
     }
     buf = buf - len;
     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSendmsg: Encapsulated");
     return S5BufWritePacket(sd, &pcon->pri->cinfo, (u_char *)buf, len, flags);
    }
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "TcpSendmsg: Default");
    return REAL(sendmsg)(sd, msg, flags);
}

