/* 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: udp.c,v 1.80.2.1.2.4 1998/07/17 20:01:52 wlu Exp $
 */

/* This file has all the main functions for handling udp connections.  In    */
/* particular it only one external function which is the main udp function,  */
/* HandleUdpConnection().                                                    */
#include "socks5p.h"
#include "threads.h"
#include "daemon.h"
#include "protocol.h"
#include "validate.h"
#include "udputil.h"
#include "msgids.h"
#include "info.h"
#include "msg.h"
#include "s2s.h"
#include "udp.h"
#include "log.h"

#ifndef UDPTIMEOUT
#define UDPTIMEOUT 15*60
#endif

#define CODEABLE(x)          ((x) && (x)->auth.encode)
#define CODEBUFF(a, b, c, d) ((a)->auth.encode(b, c, d, (a)->auth.context))
    
#define NEXTADDR(x) (((x)->nextVersion)?&((x)->sckAddr)  :&((x)->dstAddr))
#define NEXTNAME(x) (((x)->nextVersion)? ((x)->sckName): ((x)->dstName))

/* Close all the file descriptor, clean up the caches, log the exit error,   */
/* and quit...                                                               */
static void UdpCleanup(S5LinkInfo *pri, UdpInfo *u) {
    S5IOHandle i;
    void *tmp;

    if (!u) return;

    for (; u->scache ; u->scache = (struct scache *)tmp) {
	S5BufCleanContext(&u->scache->cinfo);
	RemoveIdentEntry(u->scache->idtentry);
	tmp = (void *)u->scache->next;
	free(u->scache);
    }

    S5BufCleanContext(&u->iio);

    for (i = 0; i < u->maxfd; i++) {
	if (FD_ISSET(i, &u->myfds)) CLOSESOCKET(i);
    }

    if (u->relay != S5InvalidIOHandle) CLOSESOCKET(u->relay);
		
    for (; u->acache ; u->acache = (struct acache *)tmp) {
	tmp = (void *)u->acache->next;
	free(u->acache);
    }

    for (; u->rcache ; u->rcache = (struct aroute *)tmp) {
	tmp = (void *)u->rcache->next;
	free(u->rcache);
    }

    for (; u->hcache; u->hcache = (struct hinfo *)tmp) {
	tmp = (void *)u->hcache->next;
	free(u->hcache);
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_UDP_END, "UDP Proxy Termination: (%s:%d) for user %s; %d bytes out %d bytes in",
	    pri->srcName, ntohs(lsAddr2Port(&pri->srcAddr)), pri->srcUser, pri->outbc, pri->inbc);

    free(u);
    u = NULL;
}

/* Process udp use_client_port sub-command.                                  */
static void UdpUseClientPort(S5LinkInfo *pri, UdpInfo *u, u_char *err) {
    /* do we need this ????            */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Doing USECLIENTPORT command");

    if (!lsAddrAddrComp(&pri->dstAddr, &pri->srcAddr) ||
	    pri->dstAddr.sin.sin_addr.s_addr == INADDR_ANY ||
	    pri->dstAddr.sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
    	pri->clientPort = lsAddr2Port(&pri->dstAddr);
    	*err = SOCKS5_NOERR;
    }
}

/* Process udp bind sub-command.                                             */
static void UdpBind(S5LinkInfo *pri, UdpInfo *u, u_char *err) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Doing BIND command");

    if (ResolveNames(pri) < 0) return;

    if (!lsAddrIsNull(&pri->dstAddr)) {
	*err = SOCKS5_BADADDR;
	return;
    }

    if (CheckIfCached(u, pri, 1) == 0) {
   	if (u->curs != NULL) {
	    if (S5IOCheck(u->curs->cinfo.fd) < 0) return; /* should it re-establish???    */

	    if (u->curs->reserved != S5UDP_USECTRL) {
		lsAddrCopy(&pri->intAddr, &u->curr->raddr, sizeof(S5NetAddr));
    		*err = SOCKS5_NOERR;
	 	return;
	    } else {
    		if (u->cura->bounded) lsAddrCopy(&pri->intAddr, &u->cura->baddr, sizeof(S5NetAddr));
    		else if (S5SExchgUdpCmd(u->curs->cinfo.fd, &u->curs->cinfo, pri, SOCKS5_VERSION, S5UDP_BIND, err) < 0) return;
	    }
	} else lsAddrCopy(&pri->intAddr, &u->curr->raddr, sizeof(S5NetAddr));
    } else {
    	if (CheckIfAllowed(u, pri) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "UDP Command: Bind failed: permission denied");
	    *err = SOCKS5_AUTHORIZE;
	    return;
        }

        if (pri->nextVersion == SOCKS5_VERSION && FindProxyEntry(u, pri, &pri->sckAddr, 1) < 0) {
            S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Command: Couldn't connect to proxy: %s:%d", ADDRANDPORT(&pri->sckAddr));
	    return;
        } else if (pri->nextVersion == SOCKS5_VERSION) {
	    if (pri->nextReserved != S5UDP_USECTRL) {
/*
                S5NetAddr na;
            	char msg = '\0';

    	    	lsAddrCopy(&na, &pri->dstAddr, lsAddrSize(&pri->dstAddr));
            	lsAddrSetPort(&na, 9);
            	if (lsUdpProtoSend(pcon->fd, pri, &msg, 1, 0, &na.sa, lsAddrSize(&na)) < 0) return;

	  	lsAddrCopy(&pri->intAddr, &u->curr->raddr, sizeof(S5NetAddr));
		*err = SOCKS5_NOERR;
*/
 		return;
    	    } else if (S5SExchgUdpCmd(u->curs->cinfo.fd, &u->curs->cinfo, pri, SOCKS5_VERSION, S5UDP_BIND, err) < 0) return;
        } else if (pri->nextVersion != SOCKS5_VERSION) {
	    if (FindOutSocket(u, pri, &pri->dstAddr, pri->dstName) == S5InvalidIOHandle) {
                S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Command: Couldn't make out socket to destination: %s:%d", ADDRANDPORT(&pri->dstAddr));
                return;
    	    }
        }
    }

    u->cura->bounded = 1;
    lsAddrCopy(&u->cura->baddr, &pri->intAddr, sizeof(S5NetAddr));
    *err = SOCKS5_NOERR;
}

/* Process udp getsockname sub-command.                                      */
static void UdpGetsockname(S5LinkInfo *pri, UdpInfo *u, u_char *err) {

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Doing GETSOCKNAME command");

    if (ResolveNames(pri) < 0) return;

    if (!lsAddrIsNull(&pri->dstAddr)) {
	*err = SOCKS5_BADADDR;
	return;
    }

    if (CheckIfCached(u, pri, 1) < 0) return;

    if (u->curs != NULL) {
	if (u->curs->reserved != S5UDP_USECTRL) return;
	if (S5IOCheck(u->curs->cinfo.fd) < 0) return;

    	if (u->cura->bounded) lsAddrCopy(&pri->intAddr, &u->cura->baddr, sizeof(S5NetAddr));
    	else if (S5SExchgUdpCmd(u->curs->cinfo.fd, &u->curs->cinfo, pri, SOCKS5_VERSION, S5UDP_GETSOCKNAME, err) < 0) return;
    } else lsAddrCopy(&pri->intAddr, &u->curr->raddr, sizeof(S5NetAddr));

    u->cura->bounded = 1;
    lsAddrCopy(&u->cura->baddr, &pri->intAddr, sizeof(S5NetAddr));
    *err = SOCKS5_NOERR;
}

/* Process udp sub-commands received on the tcp control channel.             */
/* Current implementation supports three sub-commands. People should be      */
/* able to add new sub-commands                                              */
static int HandleCommand(S5LinkInfo *pri, UdpInfo *u) {
    u_char cmd = 0, res = 0, s5err = SOCKS5_BADCMND;

    memset((char *)&pri->intAddr, 0, sizeof(S5NetAddr));
    pri->intAddr.sa.sa_family = AF_INET;

    if (lsReadRequest(u->iio.fd, &u->iio, &pri->dstAddr, &pri->peerVersion, &cmd, &res) < 0) {
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Read request command failed");
        return -1;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Read command (%d) request (%s:%d)", (int)cmd, ADDRANDPORT(&pri->dstAddr));

    switch (cmd) {
	case S5UDP_USECLIENTPORT:
	    UdpUseClientPort(pri, u, &s5err);
	    break;
	case S5UDP_BIND:
	    UdpBind(pri, u, &s5err);
	    break;
	case S5UDP_GETSOCKNAME:
	    UdpGetsockname(pri, u, &s5err);
	    break;
	default:
    	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Command: Unknown command");
	    break;
    }

    return lsSendResponse(u->iio.fd, &u->iio, &pri->intAddr, pri->peerVersion, s5err, 0, NULL);
}

/* Proxy a request for the client.  This basically involves checking to see  */
/* that the information is correct, then checking to see if it is allowed to */
/* send to the destination (in FindOutDestination) as well as finding the    */
/* intermediate destination...If the destination turns out to be directly    */
/* reachable, then we may reassemble a fragment and send that instead.       */
/*                                                                           */
/* Arguments: sin -- the address of the person who sent the message to us    */
static int RecvFromClient(S5Packet *packet, S5LinkInfo *pri, UdpInfo *u, S5NetAddr *sender) {
    S5Packet buf[2];

    /* Make sure the client really sent this message.  If not, assume it is  */
    /* a forgery.                                                            */
    if (lsAddrComp(&pri->srcAddr, sender)) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "UDP Client Receive: Received message from non-client (%s:%d) on client socket", ADDRANDPORT(sender));
        return -1;
    }

    /* Decode the message and copy the decode message into u->obuf.          */
    if (CODEABLE(&u->iio)) {
	buf[0].data = u->obuf; buf[0].len = u->obuflen; buf[0].off = u->obuflen;
	buf[1].data = NULL;    buf[1].len = 0;		buf[1].off = 0;
	
	if (CODEBUFF(&u->iio, &buf[0], &buf[1], S5_DECODE) < 0 || buf[1].len > UDP_MAX_PAYLOAD) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "UDP Client Receive: Error decoding message");
	    return -1;
	} else {
	    memcpy(u->obuf, buf[1].data, buf[1].len);
	    u->obuflen = buf[1].len;
	    free(buf[1].data);
	}
    }

    /* A real message would have to have a length greater than the header    */
    /* size.  Drop the message if it does not.                               */
    if (u->obuflen <= MINHDRSIZE) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Client Receive: Received invalid message from client: too short");
	return -1;
    }

    /* Extract the destination address and it's name.                        */
    if (lsGetProtoAddr(SOCKS5_VERSION, u->obuf, &pri->dstAddr) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Client Receive: Received invalid message from client: invalid address");
	return -1;
    } 

    if (ResolveNames(pri) < 0) {
	return -1;
    }

    /* Make sure we've either talked to this destination before, or we are   */
    /* allowed to talk to it...                                              */
    if (CheckIfCached(u, pri, 1) < 0 && CheckIfAllowed(u, pri) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Client Receive: send failed: permission denied");
	return -1;
    }

    packet->data = u->obuf    + HDRSIZE(u->obuf);
    packet->off  = u->obuflen - HDRSIZE(u->obuf);
    packet->len  = UDP_MAX_PAYLOAD;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Client Receive: Received valid message of length %d", u->obuflen-HDRSIZE(u->obuf));
    return u->obuflen - HDRSIZE(u->obuf);
}

/* Proxy a request back from a server. This basically involves checking to   */
/* see if this is from a server we've talked to (FindCachedSocks) in which   */
/* case we forward it on... If not, it was from the application server.      */
/* To handle this case, we first check that this server is allowed (cache?)  */
/* then, if it is, package it up (possibly fragmenting it) and ship it back  */
/* to the client.                                                            */
/*                                                                           */
/* Arguments: sin -- the address of the person who sent the message to us    */
static int RecvFromServer(S5Packet *packet, S5LinkInfo *pri, UdpInfo *u, S5NetAddr *sender) {
    S5Packet buf[2];
    
    /* If we don't find a proxy entry, zero out the appropriate structures   */
    /* so that when we look at them later, we don't do something wrong.  If  */
    /* we did find one, go ahead and look up its name for logging...         */
    pri->dstAddr = *sender;
    if (FindProxyEntry(u, pri, &pri->dstAddr, 0) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Server Receive: Received message from dead server");
	return -1;
    }

    /* If we have a decode function for the server, go ahead and decode the  */
    /* message and move it into the appropriate place                        */
    if (CODEABLE(u->oiop)) {
	buf[0].data = u->obuf; buf[0].len = u->obuflen; buf[0].off = u->obuflen;
	buf[1].data = NULL;    buf[1].len = 0;		buf[1].off = 0;

	if (CODEBUFF(u->oiop, &buf[0], &buf[1], S5_DECODE) < 0 || buf[1].len > UDP_MAX_PAYLOAD) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Server Receive: Error decoding message from server");
	    return -1;
	} else {
	    memcpy(u->obuf, buf[1].data, buf[1].len);
	    u->obuflen = buf[1].len;
	    free(buf[1].data);
	}
    }

    /* If FindProxyEntry told us this was from another socks server, strip   */
    /* off the header that it put on the message...                          */
    if (pri->nextVersion == SOCKS5_VERSION) {
	if (lsGetProtoAddr(SOCKS5_VERSION, u->obuf, &pri->dstAddr) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Server Receive: Received invalid message from server: invalid address");
	    return -1;
	} 

	u->obuflen -= HDRSIZE(u->obuf);
	u->obuf    += HDRSIZE(u->obuf);
    }

    GetName(pri->dstName, &pri->dstAddr);

    /* Make sure we've talked to the sender before.  If not, we will ignore  */
    /* this reply, assuming that this is a forgery.                          */
    if (CheckIfCached(u, pri, 1) < 0 && CheckIfAllowed(u, pri) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Server Receive: recv failed: permission denied");
        return -1;
    }

    packet->data = u->obuf;
    packet->len  = UDP_MAX_PAYLOAD;
    packet->off  = u->obuflen;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "UDP Server Receive: Received valid message of length %d", u->obuflen);
    return u->obuflen;
}

int UdpRecvMsg(S5Packet *packet, S5LinkInfo *pri, UdpInfo *u, int *dir) {
    S5IOHandle i, maxfd;
    fd_set fds;
    int rval;
    
    for (;;) {
	struct timeval inact = { UDPTIMEOUT, 0 };
	
	if (*dir & S5_DIRECTION_IN) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(15), 0, "UDP Receive: Selecting on outer sockets...");
	    maxfd = u->maxfd;
	    fds = u->myfds;
	} else {
	    maxfd = 0;
	    FD_ZERO(&fds);
	}
	
	if (*dir & S5_DIRECTION_OUT) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(15), 0, "UDP Receive: Selecting on inner socket...");
	    FD_SET(u->relay, &fds);
	    maxfd = MAX(maxfd, u->relay);
	}

	FD_SET(u->iio.fd, &fds);    
	maxfd = MAX(maxfd, u->iio.fd);

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(12), 0, "UDP Receive: Selecting...");

	switch (select(maxfd+1, &fds, NULL, NULL, &inact)) {
	    case 0:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Receive: Inactivity timeout expired");
		rval = -1;
		goto end;
	    case -1:
		if (ISSOCKETERROR(EINTR)) continue;
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Receive: select failed: %m");
		rval = -1;
		goto end;
	}
	
	if (FD_ISSET(u->iio.fd, &fds)) {
            if (S5IOCheck(u->iio.fd) < 0) rval = 0;
	    else if ((pri->peerReserved == S5UDP_USECTRL) && !HandleCommand(pri, u)) continue;
	    else {
	        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Receive: client closed connection");
	        rval = -1;
	    }

	    goto end;
	}
	    
	for (i = 0; i <= maxfd; i++) {
	    int fromlen = sizeof(S5NetAddr);
	    S5NetAddr sender;
	    
	    if (!FD_ISSET(i, &fds)) continue;

	    u->obuf    = u->mbuf         + MAXHDRSIZE;
	    u->obuflen = sizeof(u->mbuf) - MAXHDRSIZE;
	    
	    while ((u->obuflen = recvfrom(i, u->obuf, u->obuflen, 0, (ss *)&sender, &fromlen)) < 0 && ISSOCKETERROR(EINTR));

	    if (u->obuflen < 0) {
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "UDP Receive: real recvfrom failed: %m");
		rval = -1;
		goto end;
	    }
	    
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Receive: received a message from %s:%d", ADDRANDPORT(&sender));

	    if (i == u->relay) {
		if ((rval = RecvFromClient(packet, pri, u, &sender)) <= 0) continue; 
		*dir = S5_DIRECTION_OUT;
	    } else {
	    	if ((rval = RecvFromServer(packet, pri, u, &sender)) <= 0) continue;
	    	*dir = S5_DIRECTION_IN;
	    }

	    goto end;
	}
    }

  end:
    if (rval <= 0) {
	packet->data = NULL;
	packet->off  = 0;
	packet->len  = 0;
    }
    
    return rval;
}
    
int UdpSendMsg(S5Packet *packet, S5LinkInfo *pri, UdpInfo *u, int *dir) {
    S5Packet buf[2];
    S5NetAddr *dst;
    S5IOInfo *info;
    S5IOHandle fd;
    int alen;

    if (packet->data < u->mbuf + MAXHDRSIZE ||
	packet->data > u->mbuf + MAXHDRSIZE + UDP_MAX_PAYLOAD ) {
	char *tmp;

	if ((tmp = (char *)malloc(packet->off)) == NULL) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "UDP send: Malloc failed.");
	    u->obuflen = -1;
	    goto end;
	}
	
	memcpy(tmp, packet->data, packet->off);
	u->obuf    = u->mbuf      + MAXHDRSIZE;
	u->obuflen = packet->off;
	memcpy(u->obuf, tmp, u->obuflen);
	free(tmp);
    } else {
	u->obuf    = packet->data;
	u->obuflen = packet->off;
    }
    
    if (*dir == S5_DIRECTION_IN) {
	dst  = &pri->srcAddr;
	fd   = u->relay;
	info = &u->iio;
    } else {
	/* If we'll be using a proxy and we haven't talked to it yet,        */
	/* initialize the connection and add it it list of know proxies...   */
	if (pri->nextVersion == SOCKS5_VERSION && FindProxyEntry(u, pri, &pri->sckAddr, 1) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP send: Couldn't connect to %s:%d", ADDRANDPORT(&pri->sckAddr));
	    u->obuflen = -1;
	    goto end;
	}

	/* Find out how we should go out, update the byte count, and dump    */
	/* the packet if need be.                                            */
	fd   = FindOutSocket(u, pri, NEXTADDR(pri), NEXTNAME(pri));
	dst  = NEXTADDR(pri);
	info = u->oiop;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Send: Sending to %s:%d", ADDRANDPORT(dst));

    if (*dir == S5_DIRECTION_IN || pri->nextVersion == SOCKS5_VERSION) {
	if ((alen = lsGetProtoAddrLenFromAddr(SOCKS5_VERSION, &pri->dstAddr)) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Send: Error filling in address in packet header");
	    u->obuflen = -1;
	    goto end;
	}

	u->obuf -= alen;
	memset(u->obuf, 0, 3);
	lsSetProtoAddr(SOCKS5_VERSION, u->obuf, &pri->dstAddr);
	u->obuflen += HDRSIZE(u->obuf);
    }
    
    /* Encode the packet if necessary... This includes changing obuf to      */
    /* point to the buffer that the encoding creates.  We'll free that       */
    /* buffer later if need be.                                              */
    if (CODEABLE(info)) {
	
	buf[0].data = u->obuf;
	buf[0].len  = u->obuflen;
	buf[0].off  = 0;

	buf[1].data = NULL;
	buf[1].len = 0;
	buf[1].off = 0;
	
	if (CODEBUFF(info, &buf[0], &buf[1], S5_ENCODE) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Send: Error encoding message for client");
	    u->obuflen = -1;
	    goto end;
	} else {
	    u->obuf    = buf[1].data;
	    u->obuflen = buf[1].len;
	}
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Send: Sending a message of length %d", u->obuflen);

    /* Send the message and log the fact that we have.                       */
    if (sendto(fd, u->obuf, u->obuflen, 0, &dst->sa, sizeof(ssi)) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "UDP Send: Sendto failed: %m");
    } else if (*dir == S5_DIRECTION_OUT) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Send: Client Sent Message to %s:%d", ADDRANDPORT(dst));
    } else {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UDP Send: Client Recveived Message from %s:%d", ADDRANDPORT(&pri->dstAddr));
    }

    /* Free any space we had to allocate because of the encoding             */
    if (CODEABLE(info)) {
	if (buf[1].data) free(buf[1].data);
    }

  end:
    packet->data = NULL;
    packet->off  = 0;
    packet->len  = 0;
    return u->obuflen;
}

int UdpSetup(S5IOInfo *ioinfo, S5LinkInfo *pri, S5CommandInfo *cmdinfo) {
    int len = sizeof(S5NetAddr), rval = EXIT_ERR;
    S5NetAddr route;
    u_char s5err = SOCKS5_FAIL;
    UdpInfo *u = NULL;
    
    /* The destination is really the port we'll be receiving request from    */
    /* it is allowed to be 0, but we take that to mean we'll be recv'ing it  */
    /* from the same place the tcp connection came from, different port      */
    if (lsAddrAddrComp(&pri->dstAddr, &pri->srcAddr) != 0) {
	if (pri->dstAddr.sin.sin_addr.s_addr != INADDR_ANY && pri->dstAddr.sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
	    s5err = SOCKS5_BADADDR;
	    goto cleanup;
	}
    }

    /* Allocate space for the udp specific options we'll be using...         */
    if (!(cmdinfo->option = (void *)(u = (UdpInfo *)calloc(1, sizeof(UdpInfo))))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "UDP Setup: Malloc() failed");
	goto cleanup;
    }

    u->iio  = *ioinfo;
    memset(&u->myfds, 0, sizeof(fd_set));

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_UDP_START, "UDP Proxy Request: (%s:%d) for user %s", pri->srcName, ntohs(lsAddr2Port(&pri->srcAddr)), pri->srcUser);

    /* if the destination port is 0, we will assume the same port as tcp's    */
    if (lsAddr2Port(&pri->dstAddr) != (u_short)0) lsAddrSetPort(&pri->srcAddr, lsAddr2Port(&pri->dstAddr));

    /* We set the destination address same as the source for Authorize        */
    /* function to distinguish the UDP request and UDP messages ...           */
    lsAddrCopy(&pri->dstAddr, &pri->srcAddr, lsAddrSize(&pri->srcAddr));
    if (Authorize(pri, 0) != AUTH_OK) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "UDP Setup: Setup failed: permission denied");
	s5err = SOCKS5_AUTHORIZE;
	rval  = EXIT_AUTH;
	goto cleanup;
    }

    lsAddrCopy(&route, &pri->bndAddr, lsAddrSize(&pri->bndAddr));
    lsAddrSetPort(&route, (u_short)0);

    if ((u->relay = MakeOutSocket(u, &route, (u_short)0)) == S5InvalidIOHandle) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "UDP Setup: Couldn't make main udp socket: %m");
	goto cleanup;
    }

    if (getsockname(u->relay, (ss *)&route.sa, &len) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "UDP Setup: Getsockname failed on main udp socket: %m");
	goto cleanup;
    }

    lsAddrSetPort(&pri->bndAddr, lsAddr2Port(&route));

    if (lsSendResponse(u->iio.fd, &u->iio, &pri->bndAddr, pri->peerVersion, SOCKS5_RESULT, (pri->peerReserved == S5UDP_USECTRL)?S5UDP_USECTRL:0, NULL) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "UDP Setup: couldn't send response");
	rval = EXIT_NETERR;
	goto cleanup;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_UDP_ESTAB, "UDP Proxy Established: (%s:%d) for user %s",
	    pri->srcName, ntohs(lsAddr2Port(&pri->srcAddr)), pri->srcUser);

    cmdinfo->recvmsg  = (int (*)(S5Packet *, S5LinkInfo *, void *, int *))UdpRecvMsg;
    cmdinfo->sendmsg  = (int (*)(S5Packet *, S5LinkInfo *, void *, int *))UdpSendMsg;
    cmdinfo->clean    = (int (*)(S5LinkInfo *, void *))UdpCleanup;
    return EXIT_OK;

  cleanup:
    if (rval != EXIT_NETERR) lsSendResponse(ioinfo->fd, ioinfo, &pri->bndAddr, pri->peerVersion, s5err, pri->peerReserved, NULL);

    if (u != NULL) UdpCleanup(pri, u);
    else {
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_UDP_END, "UDP Proxy Termination: (%s:%d) for user %s; %d bytes out %d bytes in",
            	pri->srcName, ntohs(lsAddr2Port(&pri->srcAddr)), pri->srcUser, pri->outbc, pri->inbc);

    	S5BufCleanContext(ioinfo);
    }

    /* Prevent any further problems from being seg faults.                   */
    cmdinfo->option = NULL;
    if (rval == EXIT_OK) rval = EXIT_ERR;
    return rval;
}
