/* 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: flow.c,v 1.31.4.3 1998/03/04 16:13:39 jyou Exp $
 */

/* This file has all the function to do tcp proxying itself.  The only one   */
/* that is visible to the outside world should be HandleTcpConnection.       */
#include "socks5p.h"
#include "daemon.h"
#include "proxy.h"
#include "flow.h"
#include "log.h"
#include "msg.h"

#ifndef INACTIVITY_TIMEOUT
#define INACTIVITY_TIMEOUT 15*60 /* How much inactivity will I tolerate???   */
#endif

static int proxyinturn() {
    static int turn = 0;

    return turn = turn?0:1;
}

static int FlowSetup(S5Packet *buf) {
    char *olddata = buf->data;

    if (buf->data == NULL) {
	buf->data = malloc(GENERICBUFSIZE);
	if (buf->data) S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Setup: Allocated Buffer");
	buf->len  = GENERICBUFSIZE;
	buf->off  = 0;
    }

    if (buf->len == buf->off) {
	buf->data = realloc(olddata = buf->data, buf->len += GENERICBUFSIZE);
	if (buf->data) S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Setup: Grew Buffer");
    }

    if (buf->data == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Setup: Couldn't allocate buffer space");
	if (!olddata) free(olddata);
	return -1;
    }

    return 0;
}

int S5TcpFlowRecv(S5IOInfo *iio, S5IOInfo *oio, S5Packet *packet, int *dir) {
    S5IOHandle fdsbits = ((iio->fd > oio->fd)?iio->fd:oio->fd)+1;
    fd_set fds, xfds, bu;
    S5IOInfo *io;
    char *string;
    int n;
    int turn = 1;

    FD_ZERO(&bu);
    if (*dir & S5_DIRECTION_OUT) FD_SET(iio->fd, &bu);
    if (*dir & S5_DIRECTION_IN)  FD_SET(oio->fd, &bu);

    if (FlowSetup(packet) < 0) {
	return -1;
    }

    for (fds = bu ; ; fds = bu) {
	struct timeval tout;

	tout.tv_sec = idletimeout*60;
	tout.tv_usec = 0;
	if (!FD_ISSET(iio->fd,  &fds) && !FD_ISSET(oio->fd, &fds)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Recv: Neither file descriptor is set");
	    return -1;
	}

        xfds = fds;
	switch (select(fdsbits, &fds, NULL, &xfds, &tout)) {
	    case -1:
		if (ISSOCKETERROR(EINTR)) continue;
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Recv: Select failed: %m");
		return -1;
	    case 0:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Recv: Select failed: Inactivity timeout");
		return -1;
	}

	if (FD_ISSET(iio->fd, &xfds) && FD_ISSET(oio->fd, &xfds))
            turn = proxyinturn(); 
        else if (FD_ISSET(iio->fd, &xfds))
            turn = 1;
        else if (FD_ISSET(oio->fd, &xfds))
            turn = 0;
        else if (FD_ISSET(iio->fd, &fds) && FD_ISSET(oio->fd, &fds))
            turn = proxyinturn();
        else if (FD_ISSET(iio->fd, &fds))
            turn = 1;
	else if (FD_ISSET(oio->fd, &fds))
            turn = 0;
	else {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Recv: Invalid file descriptor set");
	    return -1;
	}

        if (turn) {
            *dir = S5_DIRECTION_OUT;
            string = "client";
            io = iio;
        } else {
            *dir = S5_DIRECTION_IN;
            string = "server";
            io = oio;
        }

        packet->oob = 0;
        if (FD_ISSET(io->fd, &xfds))
            (void) ioctl(io->fd, SIOCATMARK, (char *)&packet->oob);

#define RECV_IOFLAGS S5_IOFLAGS_TIMED|S5_IOFLAGS_RESTART

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Recv: Reading from %s socket", string);
	switch ((n = S5BufReadPacket(io->fd, io, packet->data + packet->off, packet->oob?1:packet->len - packet->off, 0))) {
	    case -1: 
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING,  0, "Flow Recv: %s Read failed: %m", string);
		return -1;
	    case 0:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Flow Recv: %s closed connection", string);
		return 0;
	    default:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Recv: Read %d bytes from %s socket", n, string);
		packet->off += n;
		return n;
	}
    }
}

int S5TcpFlowSend(S5IOInfo *iio, S5IOInfo *oio, S5Packet *packet, int *dir) {
    double timerm = (double)idletimeout*60;
    S5IOInfo *io;
    char *string;
    int n;

    switch (*dir) {
	case S5_DIRECTION_OUT:
	    string = "server";
	    io = oio;
	    break;
	case S5_DIRECTION_IN:
	    string = "client";
	    io = iio;
	    break;
	default:
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Send: Invalid direction: %d", *dir);
	    return -1;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Send: Writing %d bytes to %s socket", packet->off, string);    

#define SEND_IOFLAGS S5_IOFLAGS_TIMED|S5_IOFLAGS_RESTART|S5_IOFLAGS_NBYTES

    switch ((n = S5IOSend(io->fd, io, packet->data, packet->off, packet->oob?MSG_OOB:0, SEND_IOFLAGS, &timerm))) {
	case -1: 
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Flow Send: %s Write failed: %m", string);
	    return -1;
	case 0:
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Flow Send: %s closed connection", string);
	    return 0;
	default:
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Flow Send: Wrote %d bytes to %s", n, string);
	    packet->off -= n;
	    return n;
    }
}

