/*
 * Tentun - covert tunneling project
 *
 * Copyright (C) 2003 David Gordon
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 */

/**************************************/
/* INCLUDES */

#include "encap.h"


/**************************************/
/* FUNCTIONS */

tcp_local::tcp_local()
{
    l2t_buf.set_size(round_buf_size);
    t2l_buf.set_size(round_buf_size);
    init();
}

tcp_local::~tcp_local()
{
    clean_up();
}

void
 tcp_local::init()
{
    data_sock_open = listen_sock_open = 0;
}

void tcp_local::clean_up()
{
    // Call this function to clean up the local side.
    if (data_sock_open)
	close(data_sock);

    if (listen_sock_open)
	close(listen_sock);

    init();
}

int tcp_local::establish_l_end(int src_port)
{
    // This function opens the listen sock and bind it to src_port. The 
    // socket will be listening for connections on that port.
    // Returns 1 on error, 0 otherwise.
    int error;
    bool error_flag;

    while (1) {
	error_flag = 1;

	// Open the socket.
	error = open_ip_sock(TCP_CONNECTION, &listen_sock);
	if (error)
	    break;

	// Bind it.
	error = bind_ip_sock(&listen_sock, src_port);
	if (error) {
	    close(listen_sock);
	    break;
	}
	// Set socket listening for a connection.
	error = listen(listen_sock, 0);
	if (error == -1) {
	    close(listen_sock);
	    perror("Failed to listen on a socket");
	    break;
	}

	error_flag = 0;
	listen_sock_open = 1;
	break;
    }

    if (error_flag)
	return 1;

    end_type = local_end;

    return 0;
}

int tcp_local::wait_for_conn()
{
    // This function waits for a TCP connection on the listen port. 
    // The function will return when the connection is made. The function
    // return 1 on error, 0 otherwise. If it is successful, then the caller
    // can assume that data_sock is now open.
    int error;

    // Wait and accept a connection.
    error = accept(listen_sock, NULL, 0);
    if (error == -1) {
	perror("Cannot accept a connection");
	return 1;
    }

    if (CONN_SOCK_DEBUG) {
	printf("Accepted a connection.\n");
    }

    data_sock = error;
    data_sock_open = 1;

    return 0;
}

void tcp_local::close_l_end()
{
    // Close the current connection, but keep listening on the socket.
    // It's not an error to call this function even if the socket is
    // closed.
    if (data_sock_open)
	close(data_sock);
}

int tcp_local::establish_r_end(sockaddr_in * dest_addr_ptr, int dest_port)
{
    // Open a connection to the local host specified by dest_addr_ptr
    // and dest_port. Returns 1 on error, 0 otherwise.
    int error;

    error = establish_ip_connection(dest_addr_ptr, dest_port,
				    &data_sock, TCP_CONNECTION);

    if (error)
	return 1;

    end_type = remote_end;
    data_sock_open = 1;

    return 0;
}

void tcp_local::close_r_end()
{
    // Close the connection to the local host.
    // It's not an error to call this function even if the socket is
    // closed.
    if (data_sock_open)
	close(data_sock);
}

int tcp_local::local_to_tunnel_avail()
{
    // This function returns the size of the data available for 
    // transmission through the tunnel.
    return l2t_buf.used_size();
}

int tcp_local::tunnel_to_local_avail()
{
    // This function returns the size of the buffer still available for 
    // storing data from the tunnel.
    return t2l_buf.avail_size();
}

bool tcp_local::has_unflushed_data()
{
    // This function returns true if there is data to transfer to the
    // local socket from the tunnel.
    return (t2l_buf.used_size() > 0);
}

void tcp_local::tunnel_to_local(void *buf, int size)
{
    // Transfer local data to the tunnel.
    t2l_buf.write_buf((char *) buf, size);
}

void tcp_local::local_to_tunnel(void *buf, int size)
{
    // Transfer tunnel data to the local host.
    l2t_buf.read_buf((char *) buf, size);
}

void tcp_local::register_sock(fd_set * read_set_ptr,
			      fd_set * write_set_ptr, int *max)
{
    // This function will put the data socket in the read and write set
    // if it is interested in reading/writing to the socket. The function
    // must set max to the highest socket value it puts in the sets.
    *max = 0;

    if (data_sock_open && !l2t_buf.is_full()) {
	FD_SET((unsigned int) data_sock, read_set_ptr);
	*max = data_sock;
    }

    if (data_sock_open && !t2l_buf.is_empty()) {
	FD_SET((unsigned int) data_sock, write_set_ptr);
	*max = data_sock;
    }
}

int tcp_local::do_local_comm(bool * closed, bool * worked)
{
    // This function must be called periodically to transfer data from/to
    // the local socket. It returns 1 on error, 0 otherwise.
    // Set closed to 1 if socket was "closed".
    // Set worked to 1 if work was done.
    int error;

    *closed = 0;
    *worked = 0;

    // Loop until we can't exchange data anymore.
    while (1) {
	fd_set read_set, write_set;
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	timeval tv = { 0, 0 };
	bool work_flag = 0;

	if (data_sock_open && !l2t_buf.is_full()) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Local: I can read data from local socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &read_set);
	    work_flag = 1;
	}

	if (data_sock_open && !t2l_buf.is_empty()) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Local: I can write data to local socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &write_set);
	    work_flag = 1;
	}

	if (!work_flag) {
	    return 0;
	}

	work_flag = 0;

	error = select(data_sock + 1, &read_set, &write_set, NULL, &tv);
	if (error == -1) {
	    perror("Select failed");
	    return 1;
	}

	if (FD_ISSET((unsigned int) data_sock, &read_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Local: I'm reading on local socket.\n");
	    }

	    error = read_data_from_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (FD_ISSET((unsigned int) data_sock, &write_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Local: I'm writing on local socket.\n");
	    }

	    error = write_data_to_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (!work_flag) {
	    return 0;
	}
    }
}

int tcp_local::read_data_from_sock()
{
    // This function reads as much data as it can from the socket. This
    // function is not supposed to be called if no data can be read.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = l2t_buf.avail_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    error = read(data_sock, &tmp_trans_buf, transfer_size);
    if (error == -1) {
	perror("Reading data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Local connection closed.");
	return 1;
    }
    // Transfer the data to the round buffer. It may be less than
    // requested.
    transfer_size = error;

    l2t_buf.write_buf(tmp_trans_buf, transfer_size);

    return 0;
}

int tcp_local::write_data_to_sock()
{
    // This function writes as much data as it can from the socket. This
    // function is not supposed to be called if no data is available.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = t2l_buf.used_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    // Transfer the data to the temporary buffer. Note what we may write be 
    // less than what we read.
    t2l_buf.peek_buf(tmp_trans_buf, transfer_size);

    // Write the data to the socket.
    error = write(data_sock, &tmp_trans_buf, transfer_size);
    if (error == -1) {
	perror("Writing data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Local connection closed.");
	return 1;
    }

    transfer_size = error;

    // Seek up to the end of the data transfered.
    t2l_buf.seek(transfer_size);

    return 0;
}


/**************************************/
tcp_tunnel::tcp_tunnel()
{
    l2t_buf.set_size(round_buf_size);
    t2l_buf.set_size(round_buf_size);
    init();
}

tcp_tunnel::~tcp_tunnel()
{
    clean_up();
}

void
 tcp_tunnel::init()
{
    data_sock_open = listen_sock_open = 0;
}

void tcp_tunnel::clean_up()
{
    // Call this function to clean up the tunnel side.
    if (data_sock_open)
	close(data_sock);

    if (listen_sock_open)
	close(listen_sock);

    init();
}

int tcp_tunnel::establish_l_end(int src_port)
{
    // This function opens the listen sock and bind it to src_port. The 
    // socket will be listening for connections on that port.
    // Returns 1 on error, 0 otherwise.
    int error;
    bool error_flag;

    while (1) {
	error_flag = 1;

	// Open the socket.
	error = open_ip_sock(TCP_CONNECTION, &listen_sock);
	if (error)
	    break;

	// Bind it.
	error = bind_ip_sock(&listen_sock, src_port);
	if (error) {
	    close(listen_sock);
	    break;
	}
	// Set socket listening for a connection.
	error = listen(listen_sock, 0);
	if (error == -1) {
	    close(listen_sock);
	    perror("Failed to listen on a socket");
	    break;
	}

	error_flag = 0;
	listen_sock_open = 1;
	break;
    }

    if (error_flag)
	return 1;

    end_type = local_end;

    return 0;
}

int tcp_tunnel::wait_for_conn()
{
    // This function waits for a TCP connection on the listen port. 
    // The function will return when the connection is made. The function
    // return 1 on error, 0 otherwise. If it is successful, then the caller
    // can assume that data_sock is now open.
    int error;

    // Wait and accept a connection.
    error = accept(listen_sock, NULL, 0);
    if (error == -1) {
	perror("Cannot accept a connection");
	return 1;
    }

    if (CONN_SOCK_DEBUG) {
	printf("Accepted a connection.\n");
    }

    data_sock = error;
    data_sock_open = 1;

    return 0;
}

void tcp_tunnel::close_l_end()
{
    // Close the current connection, but keep listening on the socket.
    if (data_sock_open)
	close(data_sock);
}

int tcp_tunnel::establish_r_end(sockaddr_in * dest_addr_ptr, int dest_port)
{
    // Open a connection to the remote end of the tunnel as specified 
    // by dest_addr_ptr and dest_port. Returns 1 on error, 0 otherwise.
    int error;

    error = establish_ip_connection(dest_addr_ptr, dest_port,
				    &data_sock, TCP_CONNECTION);

    if (error)
	return 1;

    end_type = remote_end;
    data_sock_open = 1;

    return 0;
}

void tcp_tunnel::close_r_end()
{
    // Close the connection to the remote end of the tunnel.
    if (data_sock_open)
	close(data_sock);
}

int tcp_tunnel::local_to_tunnel_avail()
{
    // This function returns the size of the buffer still available for 
    // storing data to transmit to the tunnel.

    return l2t_buf.avail_size();
}

int tcp_tunnel::tunnel_to_local_avail()
{
    // This function returns the size of the data that can be read in the
    // tunnel buffer.

    return t2l_buf.used_size();
}

bool tcp_tunnel::has_unflushed_data()
{
    // This function returns true if there is data to transfer to the
    // the tunnel.
    return (t2l_buf.used_size() > 0);
}

void tcp_tunnel::tunnel_to_local(void *buf, int size)
{
    // Transfer tunnel data to the local host.
    t2l_buf.read_buf((char *) buf, size);
}

void tcp_tunnel::local_to_tunnel(void *buf, int size)
{
    // Transfer local data to the tunnel.
    l2t_buf.write_buf((char *) buf, size);
}

void tcp_tunnel::register_sock(fd_set * read_set_ptr,
			       fd_set * write_set_ptr, int *max)
{
    // This function will put the data socket in the read and write set
    // if it is interested in reading/writing to the socket. The function
    // must set max to the highest socket value it puts in the sets.
    *max = 0;

    if (data_sock_open && !t2l_buf.is_full()) {
	FD_SET((unsigned int) data_sock, read_set_ptr);
	*max = data_sock;
    }

    if (data_sock_open && !l2t_buf.is_empty()) {
	FD_SET((unsigned int) data_sock, write_set_ptr);
	*max = data_sock;
    }
}

int tcp_tunnel::do_tunnel_comm(bool * closed, bool * worked)
{
    // Set finished to 1 if socket was closed.
    // Set worked to 1 if work was done.
    int error;

    *closed = 0;

    // Loop until we can't exchange data anymore.
    while (1) {
	fd_set read_set, write_set;
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	timeval tv = { 0, 0 };
	bool work_flag = 0;

	if (data_sock_open && !t2l_buf.is_full()) {
	    if (CONN_SOCK_DEBUG) {
		printf
		    ("TCP Tunnel: I can read data from tunnel socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &read_set);
	    work_flag = 1;
	}

	if (data_sock_open && !l2t_buf.is_empty()) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Tunnel: I can write data to tunnel socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &write_set);
	    work_flag = 1;
	}

	if (!work_flag) {
	    return 0;
	}

	work_flag = 0;

	error = select(data_sock + 1, &read_set, &write_set, NULL, &tv);
	if (error == -1) {
	    perror("TCP Tunnel: Select failed");
	    return 1;
	}

	if (FD_ISSET((unsigned int) data_sock, &read_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Tunnel: I'm reading on tunnel socket.\n");
	    }

	    error = read_data_from_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (FD_ISSET((unsigned int) data_sock, &write_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("TCP Tunnel: I'm writing on tunnel socket.\n");
	    }

	    error = write_data_to_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (!work_flag) {
	    return 0;
	}
    }
}

int tcp_tunnel::read_data_from_sock()
{
    // This function reads as much data as it can from the socket. This
    // function is not supposed to be called if no data can be read.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = t2l_buf.avail_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    error = read(data_sock, &tmp_trans_buf, transfer_size);
    if (error == -1) {
	perror("Reading data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Tunnel connection closed.");
	return 1;
    }
    // Transfer the data to the round buffer. It may be less than
    // requested.
    transfer_size = error;
    t2l_buf.write_buf(tmp_trans_buf, transfer_size);

    return 0;
}

int tcp_tunnel::write_data_to_sock()
{
    // This function writes as much data as it can from the socket. This
    // function is not supposed to be called if no data is available.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = l2t_buf.used_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    // Transfer the data to the temporary buffer. Note what we may write be 
    // less than what we read.
    l2t_buf.peek_buf(tmp_trans_buf, transfer_size);

    // Write the data to the socket.
    error = write(data_sock, &tmp_trans_buf, transfer_size);
    if (error == -1) {
	perror("Writing data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Tunnel connection closed.");
	return 1;
    }

    transfer_size = error;

    // Seek up to the end of the data transfered.
    l2t_buf.seek(transfer_size);

    return 0;
}


/**************************************/
udp_tunnel::udp_tunnel()
{
    l2t_buf.set_size(round_buf_size);
    t2l_buf.set_size(round_buf_size);
    init();
}

udp_tunnel::~udp_tunnel()
{
    clean_up();
}

void
 udp_tunnel::init()
{
    data_sock_open = 0;
}

void udp_tunnel::clean_up()
{
    // Call this function to clean up the tunnel side.
    if (data_sock_open)
	close(data_sock);

    init();
}

int udp_tunnel::establish_l_end(int src_port)
{
    // This function opens the listen sock and bind it to src_port. The 
    // socket will be listening for connections on that port.
    // Returns 1 on error, 0 otherwise.
    int error;
    bool error_flag;

    while (1) {
	error_flag = 1;

	// Open the socket.
	error = open_ip_sock(UDP_CONNECTION, &data_sock);
	if (error)
	    break;

	// Bind it.
	error = bind_ip_sock(&data_sock, src_port);
	if (error) {
	    close(data_sock);
	    break;
	}

	error_flag = 0;
	data_sock_open = 1;
	break;
    }

    if (error_flag)
	return 1;

    end_type = local_end;

    return 0;
}

int udp_tunnel::wait_for_conn()
{
    // This function waits for a UDP connection on the listen port. 
    // The function will return when the connection is made. The function
    // return 1 on error, 0 otherwise. If it is successful, then the caller
    // can assume that data_sock is now open.
    int error;

    // Wait and accept a connection.
    tunnel_addr_len = sizeof(tunnel_addr);
    error =
	recvfrom(data_sock, NULL, 0, 0, &tunnel_addr, &tunnel_addr_len);

    if (error == -1) {
	perror("Failed to receive a UDP connection");
	return 1;
    }

    if (CONN_SOCK_DEBUG) {
	printf("Accepted a connection.\n");
    }

    return 0;
}

void udp_tunnel::close_l_end()
{
    // Close the current connection, but keep listening on the socket.
    if (data_sock_open)
	close(data_sock);
}

int udp_tunnel::establish_r_end(sockaddr_in * dest_addr_ptr, int dest_port)
{
    // Open a connection to the remote end of the tunnel as specified 
    // by dest_addr_ptr and dest_port. Returns 1 on error, 0 otherwise.
    int error;

    error = establish_ip_connection(dest_addr_ptr, dest_port,
				    &data_sock, UDP_CONNECTION);

    if (error)
	return 1;


    // Write some data to the socket to mark the beginning of the
    // connection.
    if (CONN_SOCK_DEBUG) {
	printf("Forcing establishment of connection.\n");
    }

    error = send(data_sock, NULL, 0, 0);
    if (error == -1) {
	perror("Writing data on socket failed");
	return 1;
    }

    end_type = remote_end;
    data_sock_open = 1;

    return 0;
}

void udp_tunnel::close_r_end()
{
    // Close the connection to the remote end of the tunnel.
    if (data_sock_open)
	close(data_sock);
}

bool udp_tunnel::has_unflushed_data()
{
    // This function returns true if there is data to transfer to the
    // the tunnel.
    return (t2l_buf.used_size() > 0);
}

void udp_tunnel::tunnel_to_local(void *buf, int size)
{
    // Transfer tunnel data to the local host.
    t2l_buf.read_buf((char *) buf, size);
}

void udp_tunnel::local_to_tunnel(void *buf, int size)
{
    // Transfer local data to the tunnel.
    l2t_buf.write_buf((char *) buf, size);
}

void udp_tunnel::register_sock(fd_set * read_set_ptr,
			       fd_set * write_set_ptr, int *max)
{
    // This function will put the data socket in the read and write set
    // if it is interested in reading/writing to the socket. The function
    // must set max to the highest socket value it puts in the sets.
    *max = 0;

    // Buffer must be empty. We transmit only one datagram at a time.
    if (data_sock_open && t2l_buf.is_empty()) {
	FD_SET((unsigned int) data_sock, read_set_ptr);
	*max = data_sock;
    }

    if (data_sock_open && !l2t_buf.is_empty()) {
	FD_SET((unsigned int) data_sock, write_set_ptr);
	*max = data_sock;
    }
}

int udp_tunnel::do_tunnel_comm(bool * closed, bool * worked)
{
    // Set finished to 1 if socket was closed.
    // Set worked to 1 if work was done.
    int error;

    *closed = 0;

    // Loop until we can't exchange data anymore.
    while (1) {
	fd_set read_set, write_set;
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	timeval tv = { 0, 0 };
	bool work_flag = 0;

	// Buffer must be empty. We transmit only one datagram at a time.
	if (data_sock_open && t2l_buf.is_empty()) {
	    if (CONN_SOCK_DEBUG) {
		printf
		    ("UDP Tunnel: I can read data from tunnel socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &read_set);
	    work_flag = 1;
	}

	if (data_sock_open && !l2t_buf.is_empty()) {
	    if (CONN_SOCK_DEBUG) {
		printf("UDP Tunnel: I can write data to tunnel socket.\n");
	    }

	    FD_SET((unsigned int) data_sock, &write_set);
	    work_flag = 1;
	}

	if (!work_flag) {
	    return 0;
	}

	work_flag = 0;

	error = select(data_sock + 1, &read_set, &write_set, NULL, &tv);
	if (error == -1) {
	    perror("UDP Tunnel: Select failed");
	    return 1;
	}

	if (FD_ISSET((unsigned int) data_sock, &read_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("UDP Tunnel: I'm reading on tunnel socket.\n");
	    }

	    error = read_data_from_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (FD_ISSET((unsigned int) data_sock, &write_set)) {
	    if (CONN_SOCK_DEBUG) {
		printf("UDP Tunnel: I'm writing on tunnel socket.\n");
	    }

	    error = write_data_to_sock();
	    *worked = 1;
	    work_flag = 1;

	    if (error) {
		close(data_sock);
		data_sock_open = 0;
		*closed = 1;
		continue;
	    }
	}

	if (!work_flag) {
	    return 0;
	}
    }
}

int udp_tunnel::read_data_from_sock()
{
    // This function reads as much data as it can from the socket. This
    // function is not supposed to be called if no data can be read.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = t2l_buf.avail_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    error =
	recvfrom(data_sock, &tmp_trans_buf, transfer_size, 0, NULL, NULL);
    if (error == -1) {
	perror("Reading data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Tunnel connection closed.");
	return 1;
    }
    // Transfer the data to the round buffer. It may be less than
    // requested.
    transfer_size = error;
    t2l_buf.write_buf(tmp_trans_buf, transfer_size);

    return 0;
}

int udp_tunnel::write_data_to_sock()
{
    // This function writes as much data as it can from the socket. This
    // function is not supposed to be called if no data is available.
    // The function returns 1 on error, 0 otherwise.
    char tmp_trans_buf[trans_buf_size];
    int transfer_size;
    int error;

    transfer_size = l2t_buf.used_size();

    if (transfer_size > trans_buf_size) {
	transfer_size = trans_buf_size;
    }

    assert(transfer_size > 0);

    // Transfer the data to the temporary buffer.
    l2t_buf.peek_buf(tmp_trans_buf, transfer_size);

    // Write the data to the socket.
    // There are two cases: if we are the local end (receiving the
    // connection), then our socket is not connected; otherwise,
    // the socket is connected.

    if (end_type == local_end) {
	error = sendto(data_sock, &tmp_trans_buf, transfer_size, 0,
		       &tunnel_addr, tunnel_addr_len);
    }

    else {
	error = send(data_sock, &tmp_trans_buf, transfer_size, 0);
    }

    if (error == -1) {
	perror("Writing data on socket failed");
	return 1;
    }

    if (error == 0) {
	display_warning("Tunnel connection closed.");
	return 1;
    }

    transfer_size = error;

    // Seek up to the end of the data transfered.
    l2t_buf.seek(transfer_size);

    return 0;
}


/**************************************/
tcp_by_generic_tcp::tcp_by_generic_tcp()
{
}

tcp_by_generic_tcp::~tcp_by_generic_tcp()
{
}

int
 tcp_by_generic_tcp::establish_l2r_tunnel(int src_port,
					  sockaddr_in * dest_addr_ptr,
					  int dest_port, bool loop)
{
    // This function establishes a tunnel for local traffic to a 
    // remote host. It returns 1 on error, 0 otherwise.
    int error;
    bool error_flag = 1;

    // Establish local connection.
    error = local_conn.establish_l_end(src_port);
    if (error)
	return 1;

    // Loop procesing the connections.
    while (1) {
	// Wait for a connection on the local socket.
	error = local_conn.wait_for_conn();
	if (error)
	    break;

	// Establish a connection to the remote host with the tunnel.
	error = tunnel_conn.establish_r_end(dest_addr_ptr, dest_port);
	if (error)
	    goto OK;

	// Do the tunnelling job.
	error = l2r_comm();
	if (error) {
	    error_flag = 0;
	    break;
	}

	if (!loop) {
	    error_flag = 0;
	    break;
	}

      OK:
	printf("Awaiting next connection...\n");
    }

    local_conn.clean_up();
    tunnel_conn.clean_up();

    return error_flag;
}

int tcp_by_generic_tcp::establish_r2l_tunnel(int src_port,
					     sockaddr_in * dest_addr_ptr,
					     int dest_port, bool loop)
{
    // This function will create a tunnel that will accept incoming
    // connections. The data received from the tunnel will be forwarded
    // to some local host. 
    int error;
    bool error_flag = 1;

    // Establish tunnel connection.
    error = tunnel_conn.establish_l_end(src_port);
    if (error)
	return 1;

    // Loop procesing the connections.
    while (1) {
	// Wait for a connection on the tunnel.
	error = tunnel_conn.wait_for_conn();
	if (error)
	    break;

	// Establish local connection.
	error = local_conn.establish_r_end(dest_addr_ptr, dest_port);
	if (error)
	    goto OK;

	// Do the tunnelling job.
	error = r2l_comm();
	if (error) {
	    error_flag = 0;
	    break;
	}

	if (!loop) {
	    error_flag = 0;
	    break;
	}

      OK:
	printf("Awaiting next connection...\n");
    }

    local_conn.clean_up();
    tunnel_conn.clean_up();

    return error_flag;
}

int tcp_by_generic_tcp::l2r_comm()
{
    // This function does local to remote communication. It returns 1
    // on error, 0 otherwise.
    int error;

    data_stream_encoder *encoder;
    data_stream_decoder *decoder;

    error = get_l2r_coder(&encoder, &decoder);
    if (error)
	return 0;

    do_comm(encoder, decoder);

    // Make sure all sockets are closed.
    local_conn.close_l_end();
    tunnel_conn.close_r_end();

    delete encoder;
    delete decoder;

    return 0;
}

int tcp_by_generic_tcp::r2l_comm()
{
    // This function does remote to local communication. It returns 1
    // on error, 0 otherwise.
    int error;

    data_stream_encoder *encoder;
    data_stream_decoder *decoder;

    error = get_r2l_coder(&encoder, &decoder);
    if (error)
	return 0;

    do_comm(encoder, decoder);

    // Make sure all sockets are closed.
    local_conn.close_l_end();
    tunnel_conn.close_r_end();

    delete encoder;
    delete decoder;

    return 0;
}

int tcp_by_generic_tcp::do_comm(data_stream_encoder * encoder,
				data_stream_decoder * decoder)
{
    // This function does local to remote communication. It returns 1
    // on error, 0 otherwise.
    int error;

    local_sock_closed = 0;
    tunnel_sock_closed = 0;

    // Loop until an error occurs.
    while (1) {
	// We're done.
	if (local_sock_closed && tunnel_sock_closed) {
	    if (CONN_SOCK_DEBUG) {
		printf("Both sockets are closed, we're done.\n");
	    }

	    break;
	}
	// Transfer data to the tunnel.
	if (!tunnel_sock_closed) {
	    // Check how much data is available to transfer.
	    int avail_read = local_conn.local_to_tunnel_avail();

	    // If the local socket is closed and all data was
	    // transfered, it's over.
	    if (local_sock_closed
		&& avail_read == 0 && !encoder->has_unflushed_data()
		&& !tunnel_conn.has_unflushed_data()) {
		if (CONN_SOCK_DEBUG) {
		    printf
			("Local socket closed and all data flushed, we're done.\n");
		}

		break;
	    }
	    // Encode some data.
	    error =
		encoder->encode(&local_conn.l2t_buf, &tunnel_conn.l2t_buf);

	    if (error)
		break;
	}
	// Transfer data to the local end.
	if (!local_sock_closed) {
	    // Check how much data is available to transfer.
	    int avail_read = tunnel_conn.tunnel_to_local_avail();

	    // If the tunnel socket is closed and all data was
	    // transfered, it's over.
	    if (tunnel_sock_closed
		&& avail_read == 0 && !decoder->has_unflushed_data()
		&& !local_conn.has_unflushed_data()) {
		if (CONN_SOCK_DEBUG) {
		    printf
			("Tunnel socket closed and all data flushed, we're done.\n");
		}

		break;
	    }
	    // Decode some data.
	    error =
		decoder->decode(&tunnel_conn.t2l_buf, &local_conn.t2l_buf);

	    if (error)
		break;
	}
	// OK, let both ends do their comm.
	error = do_sock_comm();
	if (error)
	    break;
    }

    return 0;
}

int tcp_by_generic_tcp::do_sock_comm()
{
    // This function does the actual socket communication. It returns 1 on 
    // error, 0 otherwise.
    int error;
    bool closed, work_flag;
    bool activity_flag = 0;

    error = local_conn.do_local_comm(&closed, &work_flag);
    if (error)
	return 0;

    if (closed) {
	local_sock_closed = 1;
    }

    if (work_flag)
	activity_flag = 1;

    error = tunnel_conn.do_tunnel_comm(&closed, &work_flag);
    if (error)
	return 0;

    if (closed) {
	tunnel_sock_closed = 1;
    }

    if (work_flag)
	activity_flag = 1;

    // If there was activity, do another round. Otherwise, wait 
    // for something to happen.
    if (!activity_flag) {
	fd_set read_set, write_set;
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);

	int max = 0, tmp;

	local_conn.register_sock(&read_set, &write_set, &tmp);
	max = MAX(max, tmp);

	tunnel_conn.register_sock(&read_set, &write_set, &tmp);
	max = MAX(max, tmp);

	// Select. This will wait until something happens.
	error = select(max + 1, &read_set, &write_set, NULL, NULL);

	if (error == -1) {
	    perror("Select failed");
	    return 1;
	}
    }

    return 0;
}


/**************************************/
tcp_by_generic_udp::tcp_by_generic_udp()
{
}

tcp_by_generic_udp::~tcp_by_generic_udp()
{
}

int
 tcp_by_generic_udp::establish_l2r_tunnel(int src_port,
					  sockaddr_in * dest_addr_ptr,
					  int dest_port, bool loop)
{
    // This function establishes a tunnel for local traffic to a 
    // remote host. It returns 1 on error, 0 otherwise.
    int error;
    bool error_flag = 1;

    // Establish local connection.
    error = local_conn.establish_l_end(src_port);
    if (error)
	return 1;

    // Loop procesing the connections.
    while (1) {
	// Wait for a connection on the local socket.
	error = local_conn.wait_for_conn();
	if (error)
	    break;

	// Establish a connection to the remote host with the tunnel.
	error = tunnel_conn.establish_r_end(dest_addr_ptr, dest_port);
	if (error)
	    goto OK;

	// Do the tunnelling job.
	error = l2r_comm();
	if (error) {
	    error_flag = 0;
	    break;
	}

	if (!loop) {
	    error_flag = 0;
	    break;
	}

      OK:
	printf("Awaiting next connection...\n");
    }

    local_conn.clean_up();
    tunnel_conn.clean_up();

    return error_flag;
}

int tcp_by_generic_udp::establish_r2l_tunnel(int src_port,
					     sockaddr_in * dest_addr_ptr,
					     int dest_port, bool loop)
{
    // This function will create a tunnel that will accept incoming
    // connections. The data received from the tunnel will be forwarded
    // to some local host. 
    int error;
    bool error_flag = 1;

    // Loop procesing the connections.
    while (1) {
	// Establish tunnel connection.
	error = tunnel_conn.establish_l_end(src_port);
	if (error)
	    break;

	// Wait for a connection on the tunnel.
	error = tunnel_conn.wait_for_conn();
	if (error)
	    break;

	// Establish local connection.
	error = local_conn.establish_r_end(dest_addr_ptr, dest_port);
	if (error)
	    goto OK;

	// Do the tunnelling job.
	error = r2l_comm();
	if (error) {
	    error_flag = 0;
	    break;
	}

	if (!loop) {
	    error_flag = 0;
	    break;
	}

      OK:
	printf("Awaiting next connection...\n");
    }

    local_conn.clean_up();
    tunnel_conn.clean_up();

    return error_flag;
}

int tcp_by_generic_udp::l2r_comm()
{
    // This function does local to remote communication. It returns 1
    // on error, 0 otherwise.
    int error;

    data_stream_encoder *encoder;
    data_stream_decoder *decoder;

    error = get_l2r_coder(&encoder, &decoder);
    if (error)
	return 0;

    do_comm(encoder, decoder);

    // Make sure all sockets are closed.
    local_conn.close_l_end();
    tunnel_conn.close_r_end();

    delete encoder;
    delete decoder;

    return 0;
}

int tcp_by_generic_udp::r2l_comm()
{
    // This function does remote to local communication. It returns 1
    // on error, 0 otherwise.
    int error;

    data_stream_encoder *encoder;
    data_stream_decoder *decoder;

    error = get_r2l_coder(&encoder, &decoder);
    if (error)
	return 0;

    do_comm(encoder, decoder);

    // Make sure all sockets are closed.
    local_conn.close_l_end();
    tunnel_conn.close_r_end();

    delete encoder;
    delete decoder;

    return 0;
}

int tcp_by_generic_udp::do_comm(data_stream_encoder * encoder,
				data_stream_decoder * decoder)
{
    // This function does local to remote communication. It returns 1
    // on error, 0 otherwise.
    int error;

    local_sock_closed = 0;
    tunnel_sock_closed = 0;

    // Loop until an error occurs.
    while (1) {
	// We're done.
	if (local_sock_closed && tunnel_sock_closed) {
	    if (CONN_SOCK_DEBUG) {
		printf("Both sockets are closed, we're done.\n");
	    }

	    break;
	}
	// Transfer data to the tunnel.
	if (!tunnel_sock_closed) {
	    // Check how much data is available to transfer.
	    int avail_read = local_conn.local_to_tunnel_avail();

	    // If the local socket is closed and all data was
	    // transfered, it's over.
	    if (local_sock_closed
		&& avail_read == 0 && !encoder->has_unflushed_data()
		&& !tunnel_conn.has_unflushed_data()) {
		if (CONN_SOCK_DEBUG) {
		    printf
			("Local socket closed and all data flushed, we're done.\n");
		}

		break;
	    }
	    // We can't transmit more than one datagram at a time.
	    if (tunnel_conn.l2t_buf.used_size() == 0) {
		// Encode some data.
		error = encoder->encode(&local_conn.l2t_buf,
					&tunnel_conn.l2t_buf);

		if (error)
		    break;
	    }
	}
	// Transfer data to the local end.
	if (!local_sock_closed) {
	    // Check how much data is available to transfer.
	    int avail_read = !tunnel_conn.t2l_buf.is_empty();

	    // If the tunnel socket is closed and all data was
	    // transfered, it's over.
	    if (tunnel_sock_closed
		&& avail_read == 0 && !decoder->has_unflushed_data()
		&& !local_conn.has_unflushed_data()) {
		if (CONN_SOCK_DEBUG) {
		    printf
			("Tunnel socket closed and all data flushed, we're done.\n");
		}

		break;
	    }
	    // Decode some data.
	    error =
		decoder->decode(&tunnel_conn.t2l_buf, &local_conn.t2l_buf);

	    if (error)
		break;
	}
	// OK, let both ends do their comm.
	error = do_sock_comm();
	if (error)
	    break;
    }

    return 0;
}

int tcp_by_generic_udp::do_sock_comm()
{
    // This function does the actual socket communication. It returns 1 on 
    // error, 0 otherwise.
    int error;
    bool closed, work_flag;
    bool activity_flag = 0;

    error = local_conn.do_local_comm(&closed, &work_flag);
    if (error)
	return 0;

    if (closed) {
	local_sock_closed = 1;
    }

    if (work_flag)
	activity_flag = 1;

    error = tunnel_conn.do_tunnel_comm(&closed, &work_flag);
    if (error)
	return 0;

    if (closed) {
	tunnel_sock_closed = 1;
    }

    if (work_flag)
	activity_flag = 1;

    // If there was activity, do another round. Otherwise, wait 
    // for something to happen.
    if (!activity_flag) {
	fd_set read_set, write_set;
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);

	int max = 0, tmp;

	local_conn.register_sock(&read_set, &write_set, &tmp);
	max = MAX(max, tmp);

	tunnel_conn.register_sock(&read_set, &write_set, &tmp);
	max = MAX(max, tmp);

	// Select. This will wait until something happens.
	error = select(max + 1, &read_set, &write_set, NULL, NULL);

	if (error == -1) {
	    perror("Select failed");
	    return 1;
	}
    }

    return 0;
}
