/* Copyright (c) 1995-1999 NEC USA, Inc.  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: s2s.c,v 1.24.4.4 1999/02/23 16:42:12 wlu Exp $
 */

#include "socks5p.h"
#include "daemon.h"
#include "protocol.h"
#include "validate.h"
#include "sident.h"
#include "info.h"
#include "log.h"

/* Not sure when/if this is ever necessary during failed connects...         */
static int Reset(S5IOInfo *info, int otype) {
    S5IOHandle nfd;

    if ((nfd = socket(AF_INET, otype, 0)) == S5InvalidIOHandle) return -1;

    CLOSESOCKET(info->fd);
    info->fd = nfd;
    return 0;
}

int S5SExchangeProtocol(S5IOInfo *iiop, S5IOInfo *oiop, S5LinkInfo *pri, char *ibuf, S5NetAddr *dest, S5NetAddr *resp) {
    int i, rval = -1, optval = 1, optlen = sizeof(int);
    S5NetAddr route;
    u_char errbyte = 0; 
    char effuser[S5_USERNAME_SIZE];
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),     0, "S2S Using proxy (version %d) %s:%d", pri->nextVersion, ADDRANDPORT(&pri->sckAddr));

    if (!pri->nextVersion || !pri->nAltSckAddrs) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "S2S: No proxy to connect to.");
	return 0;
    }

    GetRoute(&pri->sckAddr, pri->sckName, "tcp", &route);

    if (bind(oiop->fd, &route.sa, lsAddrSize(&route)) == 0) {
        rval = connect(oiop->fd, &pri->sckAddr.sa, lsAddrSize(&pri->sckAddr));
    }

    if (rval < 0 && pri->peerCommand == SOCKS_UDP) { /* multiple server is not supported yet */
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S: Failed to connected to proxy at %s:%d", ADDRANDPORT(&pri->sckAddr));
	goto error;
    } else if (rval < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S: Failed to connected to proxy at %s:%d", ADDRANDPORT(&pri->sckAddr));

        getsockopt(oiop->fd, SOL_SOCKET, SO_TYPE, (char *)&optval, &optlen);

        for (i = 1; i < pri->nAltSckAddrs; i++) {
	    if (Reset(oiop, optval) < 0) goto error;

    	    lsAddrCopy(&pri->sckAddr, &pri->altSckAddrs[i], lsAddrSize(&pri->altSckAddrs[i]));
    	    GetName(pri->sckName, &pri->sckAddr);
	    GetRoute(&pri->sckAddr, pri->sckName, "tcp", &route);

	    if (bind(oiop->fd, &route.sa, lsAddrSize(&route)) < 0) goto error;

	    if (connect(oiop->fd, &pri->altSckAddrs[i].sa, lsAddrSize(&pri->altSckAddrs[i])) == 0) break;
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S: Failed to connected to proxy at %s:%d", ADDRANDPORT(&pri->altSckAddrs[i]));
        }

        if (i == pri->nAltSckAddrs) goto error;
    }

    if (ibuf) MakeIdentEntry(iiop->fd, oiop->fd, pri, ibuf);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),     0, "S2S Connected to proxy %s:%d", ADDRANDPORT(&pri->sckAddr));

    MUTEX_LOCK(gpw_mutex);
    strcpy(effuser, lsEffUser());
    MUTEX_UNLOCK(gpw_mutex);

    if (lsProtoExchg(oiop->fd, oiop, dest, effuser, pri->nextVersion, pri->peerCommand, pri->peerReserved) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S Protocol exchange with proxy (%s:%d) failed", ADDRANDPORT(&pri->sckAddr));
	goto error;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),     0, "S2S Sent message to proxy (dest %s:%d)", ADDRANDPORT(dest));

    if (lsReadResponse(oiop->fd, oiop, resp, pri->nextVersion, &errbyte, &pri->nextReserved) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S Recieved bad reply (%d) from proxy (%s:%d)", (int)errbyte, ADDRANDPORT(&pri->sckAddr));
	goto error;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),     0, "S2S Received good reply from proxy");

    /* socks4 often put 0's in the reply's address field so we have to replace them with our */
    /* best guess...                                                                         */
    if (resp->sa.sa_family == AF_INET && (resp->sin.sin_addr.s_addr == INADDR_ANY || resp->sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK))) {
        resp->sin.sin_addr.s_addr = pri->sckAddr.sin.sin_addr.s_addr;
    }

    return 0;

error:
    SETSOCKETERROR(ECONNREFUSED);
    return -1;
}

int S5SExchgUdpCmd(S5IOHandle io, S5IOInfo *info, S5LinkInfo *pri, u_char version, u_char cmd, u_char *err) {
    u_char flags;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10),     0, "S2S Exchange UDP command (%d) for address (%s:%d)", (int)cmd, ADDRANDPORT(&pri->dstAddr));

    if (lsSendRequest(io, info, &pri->dstAddr, version, cmd, 0, NULL) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,   0, "S2S Fail to send UDP command");
 	return -1;
    }

    return lsReadResponse(io, info, &pri->intAddr, version, err, &flags);
}
