/* 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_udp.c,v 1.61.2.1.2.2 1998/06/29 22:43:58 salman Exp $
 */

#include "socks5p.h"
#include "buffer.h"
#include "addr.h"
#include "protocol.h"
#include "wrap.h"
#include "libproto.h"
#include "hostname.h"
#include "cache.h"
#include "log.h"
#include "msg.h"

#define CODEABLE(x) ((x) && (x)->cinfo.auth.encode)
#define AINFO(x)    ((x)->cinfo.auth)

#define SETVERS(x, y)   ((x)[0] = (y))
#define SETRESV(x, y)   memset(x, 0, 2)
#define SETFRAG(x, y)   ((x)[2] = (y))

/* implements the SOCKS_SEND protocol, sending a message to the server, and  */
/* perhaps sometime down the road receiving an advisory deny-send message.   */
/* adds the header (toport, toaddr, fragment info(if any)) to the message    */  
/* message and sends it to the proxy                                         */
/*                                                                           */
/* C==>P  <resv(2)><frag(1)><atype(1)><host(4/v)><port(2)><data>             */
/*                                                                           */
/* returns the number of bytes sent on success; -1 on failure                */
static IORETTYPE lsProtoSend(S5IOHandle fd, lsProxyInfo *pri, const IOPTRTYPE msg, IOLENTYPE len, int flags, const ss *dest, int dstlen) {
    char *hostname, header[UDP_MAX_PAYLOAD];
    int	headerlen, rval, nlen;
    S5NetAddr name;
    S5Packet buf[2];
    
    memset(&name, 0, sizeof(S5NetAddr));

    if ((hostname = lsGetCachedHostname((S5NetAddr *)dest))) {
	name.sa.sa_family = AF_S5NAME;
	name.sn.sn_port   = lsAddr2Port((S5NetAddr *)dest);
	strcpy(name.sn.sn_name, hostname);
    } else {
	lsAddrCopy(&name, (S5NetAddr *)dest, dstlen);
    }

    SETVERS(header, 0);
    SETRESV(header, 0);                                 /* unused - reserved */
    SETFRAG(header, 0);                                 /* unused - fragment */
    lsSetProtoAddr(SOCKS5_VERSION, header, &name);
    
    if ((nlen = len + (headerlen = HDRSIZE(header))) > UDP_MAX_PAYLOAD) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS sendto: Message too big for encapsulation...");
	SETSOCKETERROR(EMSGSIZE);
	return -1;
    }

    memcpy(header + headerlen, (char *)msg, len);

    if (CODEABLE(pri)) {
	buf[0].data = header;
	buf[0].len  = nlen;
	buf[1].data = NULL;
	buf[1].len  = 0;

	if (AINFO(pri).encode(&buf[0], &buf[1], S5_ENCODE, AINFO(pri).context) < nlen) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS sendto: Encode failed...");
	    SETSOCKETERROR(EFAULT);
	    return -1;
	}

	/* If the buffer was too big, bail.  Otherwise, copy it...           */
	if ((nlen = buf[1].len) > UDP_MAX_PAYLOAD) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS sendto: Encoded Message too big for encapsulation...");
	    SETSOCKETERROR(EMSGSIZE);
	    return -1;
	}

	memcpy(header, buf[1].data, buf[1].len);
	free(buf[1].data);
	nlen = buf[1].len;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS sendto: Sent %d byte msg to proxy %s:%d...", nlen, ADDRANDPORT(&pri->prxyin));
    rval = REAL(sendto)(fd, header, nlen, flags, (ss *)&pri->prxyin, sizeof(ssi));
    return (rval == nlen)?len:-1;
}

/* extracts the header info from msg and fills in the appropriate fields...  */
/* returns 0 on success and -1 on failure...                                 */
static int lsUdpExtractHeader(char *msg, int n, int *headerlen, ss *from, int fromlen) {
    S5NetAddr netaddr;
    
    /* Here we make sure that we really go a valid message...                */
    if (n < (*headerlen = HDRSIZE(msg))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS recvfrom: invalid message");
	return -1;
    } 

    /* Extract the address from the header, and mark how long the header was */
    if (lsGetProtoAddr(SOCKS5_VERSION, msg, &netaddr) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS recvfrom: invalid message");
	return -1;
    }

    /* If the result came back as a name, we sent it as a name, so we can    */
    /* just call gethostbyname on the name to find out what address we faked */
    /* it as...                                                              */
    switch (netaddr.sa.sa_family) {
	case AF_S5NAME:
	    lsGetCachedAddress(netaddr.sn.sn_name, (S5NetAddr *)from);
	    lsAddrSetPort((S5NetAddr *)from, netaddr.sn.sn_port);
	    break;
	default:
	    lsAddrCopy((S5NetAddr *)from, &netaddr, fromlen);
	    break;
    }
	    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKS recvfrom: %s:%d", ADDRANDPORT((S5NetAddr *)from));
    return 0;
}

/* wrapper around the connect system call.                                   */
int lsUdpConnect(S5IOHandle sd, CONST ss *name, int namelen) {
    lsSocksInfo *pcon = lsConnectionFind(sd);

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

    /* if they were just trying to delete the current connection, do it.     */
    if (name == NULL) {
	if (pcon) lsConnectionDel(sd);
	return 0;
    }

    /* If we think this isn't a udp socket, check to see if it's still ok.   */
    /* If it is, return EISCONN as errno, otherwise delete it -- invalid.    */
    if (pcon) {
	if (pcon->cmd != SOCKS_UDP) {
	    if (S5IOCheck(pcon->fd) >= 0) {
		SETSOCKETERROR(EISCONN);
		return -1;
	    }

	    lsConnectionDel(sd);
	    pcon = NULL;
	}

	/* If we're already talking to the right person, we're done...       */
	if (pcon && ADDRCOMP((ssi *)name, &pcon->peer.sin)) return 0;
    }

    /* Make a connection since one didn't exit... or qthe address was wrong   */
    if ((pcon = lsLibProtoExchg(sd, (S5NetAddr *)name, SOCKS_UDP)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpConnect: Protocol exchange failed");	
	return -1;
    }

    pcon->status = CON_ESTABLISHED;
    if (!pcon->cur || pcon->cur->how == DIRECT) return REAL(connect)(sd, (ss *)name, namelen);
    return 0;
}

static int proxy_bind(S5IOHandle sd, const S5NetAddr *dst) {
    lsSocksInfo *pcon;
    lsProxyInfo *pri;
    S5NetAddr na;
    int rval = 0;

    /* First establish the UDP session. Then exchange UDP_BIND sub-command    */
    /* If the server doesn't support UDP sub-command, do the DISCARDHACK...   */
    lsAddrCopy(&na, dst, lsAddrSize(dst));
    if ((pcon = lsLibProtoExchg(sd, &na, SOCKS_UDP)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "proxy_bind: Protocol exchange failed");	
	return -1;
    }

    if ((pri = pcon->cur) != NULL && pri->how != DIRECT) {
	if (pri->reserved == S5UDP_USECTRL) {
	    if (pcon->myport != (u_short)0) lsAddrSetPort(&na, pcon->myport);
	    rval = lsLibExchgUdpCmd(pcon, &na, S5UDP_BIND);
	} else { /* DISCARDHACK                                                 */
	    char msg = '\0';

	    lsAddrSetPort(&na, 9);
            rval = lsProtoSend(pcon->fd, pri, &msg, 1, 0, &na.sa, lsAddrSize(&na));
	}
    }

    if (rval < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "proxy_bind: UDP bind failed");
        return -1;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "proxy_bind: Done");
    return 0;
}

int lsUdpBind(S5IOHandle sd, CONST ss *name, int namelen) {
    lsSocksInfo *pcon;

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

    /* keep a record of binded socket for future use                          */
    pcon = lsLibProtoExchg(sd, NULL, SOCKS_UDP);

    /* More checking is needed, such as connected UDP socket, etc...          */
    if (!lsLastCon || !lsLastCon->pri || lsLastCon->pri->how != SOCKS5_VERSION) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpBind: No prior connection found, binding locally");
	return REAL(bind)(sd, (ss *)name, namelen);
    }

    if (((ssi *)name)->sin_port != (u_short)0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpBind: bind local to %d", (int)((ssi *)name)->sin_port);
    	if (REAL(bind)(sd, (ss *)name, namelen) < 0) return -1;
    }

    if (proxy_bind(sd, &lsLastCon->peer) < 0) {
	if (pcon && pcon->pri) lsProxyCacheDel(pcon, pcon->pri);
	return -1;
    }

    pcon->status = CON_BOUND;
    return 0;
}

/* wrapper around the send system call.                                      */
IORETTYPE lsUdpSend(S5IOHandle sd, const IOPTRTYPE msg, IOLENTYPE len, int flags) {
    lsSocksInfo *pcon = lsConnectionFind(sd);
    S5NetAddr junk;

    /* If we can't find a valid connection, for whatever reason, return a    */
    /* direct send...                                                        */
    if (!pcon || pcon->status == CON_NOTESTABLISHED) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpSend: No valid connection found, returning direct send");
	return REAL(send)(sd, msg, len, flags);
    }

    junk = pcon->peer;
    
    /* PROXY, see if the control connection is still there; if not reconnect */
    if ((pcon = lsLibProtoExchg(sd, &junk, SOCKS_UDP)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpSend: Protocol exchange failed");
	return -1;
    }

    pcon->status = CON_ESTABLISHEDSEND;

    if (!pcon->cur || pcon->cur->how == DIRECT) return REAL(send)(sd, msg, len, flags);
    return lsProtoSend(sd, pcon->cur, msg, len, flags, &pcon->peer.sa, lsAddrSize(&pcon->peer));
}

/* wrapper around the sendto system call.                                    */
IORETTYPE lsUdpSendto(S5IOHandle s, const IOPTRTYPE msg, IOLENTYPE len, int flags, ss *to, int tolen) {
    lsSocksInfo *pcon = lsConnectionFind(s);

    /* Apparently some OSs allow a NULL address to mean send.                */
    /* Unfortunately, this may get us caught in a loop, where lsUdpSendto    */
    /* calls lsUdpSend which calls sendto which calls lsUdpSendto... So we   */
    /* have all this DontLoop code to protect against that...                */
    if (to == NULL) return lsUdpSend(s, msg, len, flags);

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKSsendto %s:%d...", ADDRANDPORT((S5NetAddr *)to));
    
    /* only ok to sendto() the person we're connected to...                  */
    if (pcon && (pcon->status == CON_ESTABLISHED || pcon->status == CON_ESTABLISHEDSEND)) {
	if (!ADDRCOMP((ssi *)to, &pcon->peer.sin)) {
	    SETSOCKETERROR(EISCONN);
	    return -1;
    	} else return lsUdpSend(s, msg, len, flags);
    }

    /* No udp association for this socket, so send directly                  */
    if ((pcon = lsLibProtoExchg(s, (S5NetAddr *)to, SOCKS_UDP)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpSendto: Protocol exchange failed");
	return -1;
    }

    pcon->status = CON_SENDTO;

    /* Don't send a message using the wrong protocol...                      */
    if (!pcon->cur || pcon->cur->how != SOCKS5_VERSION) {
	return REAL(sendto)(s, msg, len, flags, to, tolen);
    }

    /* Send the message directly...                                          */
    return lsProtoSend(s, pcon->cur, msg, len, flags, to, tolen);
}

/* wrapper around the recvfrom system call.                                  */
IORETTYPE lsUdpRecvfrom(S5IOHandle s, IOPTRTYPE msg, IOLENTYPE len, int flags, ss *from, int *fromlen, int isRecv) {
    lsSocksInfo *pcon = lsConnectionFind(s);
    char recvbuf[UDP_MAX_PAYLOAD+1], *obuf = NULL;
    int olen = 0, oset, fl = sizeof(ssi);
    lsProxyInfo *pri;
    ssi tmp;
    S5Packet buf[2];
    
    if (!isRecv) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKSrecvfrom...");
    } else {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKSrecv...");
    }

    if (!from) {
	from = (ss *)&tmp;
	fromlen = &fl;
    }

    if (!pcon || pcon->cmd != SOCKS_UDP) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "No valid connection found, returning direct recvfrom");
	return REAL(recvfrom)(s, msg, len, flags, from, fromlen);
    }

    /* Do we need to check whether the socket has sent a message or binded   */
    /* remotely? We might need to do a remote bind here if neither is true.. */
    /*                                                                       */
    /* If it is locally binded and remote tcp connection is available, do a  */
    /* remote bind.                                                          */
    if (pcon->status == CON_NOTESTABLISHED) {
	if (lsLastCon != NULL) proxy_bind(s, &lsLastCon->peer);
	pcon->status = CON_RECV;
    }

    for (oset = 0, obuf = recvbuf;; oset = 0, obuf = recvbuf) {
	/* If we recv'd a bad message and we were were just peeking, consume */
	/* it from the input stream so we don't get it again...              */
	if (olen != 0 && flags & MSG_PEEK) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Purging old message");
	    REAL(recvfrom)(s, recvbuf, len, flags&~MSG_PEEK, from, fromlen);
	}

	/* get a new message                                                 */
	if ((olen = REAL(recvfrom)(s, recvbuf, UDP_MAX_PAYLOAD, flags, from, fromlen)) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "recvfrom failed: %m");
	    return -1;
	}

	/* verify the contents as being from the right person                */
	if ((pri = (isRecv?pcon->cur:lsProxyCacheFind(pcon, (S5NetAddr *)from, SOCKS5_VERSION, 1))) == NULL || pri->how != SOCKS5_VERSION) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Direct recvfrom (%x;%d)", pri, pri?pri->how:0);
	    goto done;
	}

	/* verify if the server is alive. If not, discard the packet and     */
	/* and delete the proxy cache....                                    */
        if (S5IOCheck(pri->cinfo.fd) < 0) {
	    lsProxyCacheDel(pcon, pri);
	    continue;
        }

	if (!ADDRCOMP(&pri->prxyin.sin, (ssi *)from)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Recv from wrong host");
	    continue;
	}

	if (CODEABLE(pri)) {
	    buf[0].data = recvbuf;
	    buf[0].len  = olen;
	    buf[0].off  = olen;
	    buf[1].data = NULL;
	    buf[1].len  = 0;
	    buf[1].off  = 0;
	
	    if (AINFO(pri).encode(&buf[0], &buf[1], S5_DECODE, AINFO(pri).context) < 0) {
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Decoding failed");
		continue;
	    }

	    obuf = buf[1].data;
	    olen = buf[1].len;
	}

	if (lsUdpExtractHeader(obuf, olen, &oset, from, *fromlen) != 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Header extraction failed");
	    if (obuf != recvbuf) free(obuf);
	    continue;
	}

	if (isRecv && (pcon->status == CON_ESTABLISHED || pcon->status == CON_ESTABLISHEDSEND)) {
	    if (!ADDRCOMP(&pcon->peer.sin, (ssi *)from)) {
	        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Recv from wrong host");
	        if (obuf != recvbuf) free(obuf);
	        continue;
	    }
	}

      done:
	if (*fromlen > sizeof(ssi)) *fromlen = sizeof(ssi);
	memcpy((char *)msg, obuf + oset, olen = MIN((int)len, olen - oset));
	if (obuf != recvbuf) free(obuf);
	return olen;
    }
}

/* wrapper around the getpeername system call.                               */
/* returns 0 on success; -1 on failure                                       */
int lsUdpGetpeername(S5IOHandle sd, ss *name, int *namelen) {
    lsSocksInfo *pcon = lsConnectionFind(sd);

    if (!pcon || pcon->cmd != SOCKS_UDP || (pcon->status != CON_ESTABLISHED && pcon->status != CON_ESTABLISHEDSEND)) 
	return REAL(getpeername)(sd, name, namelen);

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

    return 0;
}

/* wrapper around the getpeername system call.                               */
/* returns 0 on success; -1 on failure                                       */
int lsUdpGetsockname(S5IOHandle sd, ss *name, int *namelen) {
    int rval;
    S5NetAddr junk;
    lsProxyInfo *pri;
    lsSocksInfo *pcon = lsConnectionFind(sd);

    rval = REAL(getsockname)(sd, name, namelen);
    if (!pcon || pcon->cmd != SOCKS_UDP || !lsAddrIsNull(&pcon->peer) || rval < 0) return rval;

    junk = pcon->peer;
    pcon = lsLibProtoExchg(sd, &junk, SOCKS_UDP);

    if (!pcon || !(pri = pcon->cur) || pri->reserved != S5UDP_USECTRL) return rval;

    if (lsAddr2Port(&pri->prxyout) == (u_short)0 && lsLibExchgUdpCmd(pcon, &junk, S5UDP_GETSOCKNAME) < 0) return -1;

    ((ssi *)name)->sin_addr.s_addr = INADDR_ANY;
    lsAddrSetPort((S5NetAddr *)name, lsAddr2Port(&pri->prxyout));
    if (*namelen > sizeof(ssi)) *namelen = sizeof(ssi);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsUdpGetSockname: %s:%d", ADDRANDPORT((S5NetAddr *)name));
    return 0;
}

IORETTYPE
lsUdpSendmsg(S5IOHandle s, ms *msg, int flags)
{
ss *to;
int tolen, i, len=0;
char *buf;
S5NetAddr dst;
lsSocksInfo *pcon = lsConnectionFind(s);
 
    memset(&dst, 0 , sizeof(S5NetAddr));
 
    to = (struct sockaddr *) msg->msg_name;
    tolen = sizeof((struct sockaddr *) msg->msg_name);
 
    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;
 
    if(!to)
     return lsUdpSend(s, buf, len, flags);
    else
     lsAddrCopy(&dst, (S5NetAddr *)to, lsAddrSize((S5NetAddr *)to));
 
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,"SOCKSsendmsg %s:%d..",\
                ADDRANDPORT(&dst));

    /* only ok to sendmsg() the person we're connected to.   */
 
    if(pcon&&(pcon->status == CON_ESTABLISHED || \
             pcon->status == CON_ESTABLISHEDSEND)) {
     if(!ADDRCOMP((ssi *)to, &pcon->peer.sin)) {
      SETSOCKETERROR(EISCONN);
      return -1;
     }
     else
      return lsUdpSend(s, buf, len, flags);
    }
 
    /* No udp association for this socket, so send directly     */
 
    if((pcon = lsLibProtoExchg(s, &dst, SOCKS_UDP)) == NULL) {
     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,\
                    "lsUdpSendmsg: Protocol exchange failed");
     SETSOCKETERROR(EBADF);
     return -1;
    }
   
    pcon->status = CON_SENDTO;
 
    /* Don't send a message using the wrong protocol...         */
 
    if(!pcon->cur || pcon->cur->how != SOCKS5_VERSION)
     return REAL(sendmsg)(s, msg, flags);
 
 
    /* Send the message directly...                      */

    return lsProtoSend(s, pcon->cur, (u_char *)buf, len, flags, to, tolen);
}


IORETTYPE
lsUdpRecvmsg(S5IOHandle s, ms *msg, int flags)
{
u_char recvbuf[UDP_MAX_PAYLOAD+1], *obuf = NULL;
int olen = 0, oset, fl = sizeof(ssi);
lsProxyInfo *pri;
ss          *from;
int         fromlen;
int         len=0;
int         i;
ssi tmp;
lsSocksInfo *pcon = lsConnectionFind(s);
 
 
 
    S5Packet buf[2];
   
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "SOCKSrecvmsg...");
 
    from = (struct sockaddr *) msg->msg_name;
    fromlen = sizeof((struct sockaddr *) msg->msg_name);
    for(i=0; i<msg->msg_iovlen; i++)
     len += msg->msg_iov[i].iov_len;
 
 
    if(!from) {
     from = (ss *)&tmp;
     fromlen = fl;
    }
 
    if(!pcon || pcon->cmd != SOCKS_UDP) {
     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,\
                "No valid connection found, returning direct recvmsg");
     return REAL(recvmsg)(s, msg, flags);
    }
 
/* Do we need to check whether the socket has sent a message or bound    */
/* remotely? We might need to do a remote bind here if neither is true.. */
/*                                                                       */
/* If it is locally bound and remote tcp connection is available, do a  */
/* remote bind.                                                         */
 
    if(pcon->status == CON_NOTESTABLISHED) {
     if(lsLastCon != NULL && proxy_bind(s, &lsLastCon->peer) < 0)
      return -1;
     pcon->status = CON_RECV;
    }
 
    for(oset = 0, obuf = recvbuf;; oset = 0, obuf = recvbuf) {
     if(olen != 0 && (flags & MSG_PEEK)) {
      S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,"Purging old message");
      REAL(recvmsg)(s, msg, flags&~MSG_PEEK);
     }
 
/* get a new message    */
 
     if((olen = REAL(recvmsg)(s, msg, flags )) < 0) {
      S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),0,"recvmsg failed: %m");
      return -1;
     }
 
    /* verify the contents as being from the right person */

     if((pri = (lsProxyCacheFind(pcon, (S5NetAddr *)from,SOCKS5_VERSION, 1))) == NULL || \
		pri->how != SOCKS5_VERSION) {
      S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Direct recvmsg (%x;%
d)", pri, pri?pri->how:0);
      goto done;
     }
 
    /* verify if the server is alive. If not, discard the packet and     */
    /* and delete the proxy cache....                                    */
 
     if(S5IOCheck(pri->cinfo.fd) < 0) {
      lsProxyCacheDel(pcon, pri);
      continue;
     }
 
     if(!ADDRCOMP(&pri->prxyin.sin, (ssi *)from)) {
      S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,"Recvmsg from wrong host");
      continue;
     }
 
     if(CODEABLE(pri)) {
      buf[0].data = recvbuf;
      buf[0].len  = olen;
      buf[0].off  = olen;
      buf[1].data = NULL;
      buf[1].len  = 0;
      buf[1].off  = 0;
      if(AINFO(pri).encode(&buf[0],&buf[1],S5_DECODE,AINFO(pri).context) < 0) {
       S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Decoding failed");
       continue;
      }
 
      obuf = buf[1].data;
      olen = buf[1].len;
     }
 
     if(lsUdpExtractHeader(obuf, olen, &oset, from, fromlen) != 0) {
      S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,"Header extraction failed");
      if(obuf != recvbuf)
       free(obuf);
      continue;
     }
 
     if((pcon->status == CON_ESTABLISHED || pcon->status == CON_ESTABLISHEDSEND)) {
      if (!ADDRCOMP(&pcon->peer.sin, (ssi *)from)) {
       S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0,"Recvmsg from wrong host");
       if(obuf != recvbuf)
        free(obuf);
       continue;
      }         /* if (!ADDRCOMP(&pcon->peer.sin, (ssi *)from)) */
     }          /* if(isRecv && (pcon->status == CON_ESTABLISHED....*/
 
    done:
     if(fromlen > sizeof(ssi))
      fromlen = sizeof(ssi);
 
     memcpy((u_char *)msg, obuf + oset, olen = MIN((int)len, olen - oset));
     if(obuf != recvbuf)
      free(obuf);
     return olen;
    }                   /* for(oset = 0, obuf = recvbuf;;   ...) */
}


