/* Server for the Midnight Commander Virtual File System.
   This has nothing to do with cryptography.
   Routines for the tcp connection, includes the primitive rpc routines.
   
   Copyright (C) 1995, 1996 Miguel de Icaza
   
   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.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include "net-includes.h"
#include "diffie/diffie-socket.h"
#include "diffie/z-socket.h"
#include "tcputil.h"
/* #include "../src/dialog.h" */	/* for message () */
#include "mem.h"		/* for bcopy */
#include "util.h"	/* for unix_error_string */
#include "diffie/compat.h"
#include "mad.h"
/* #include "mcfs.h" */		/* for mcserver_port definition */

#define CHECK_SIG_PIPE(sock) if (got_sigpipe) \
     { tcp_invalidate_socket (sock); return got_sigpipe = 0; }

extern void tcp_invalidate_socket (int);

int got_sigpipe;

/* Reads a block on dest for len bytes from sock */
/* Returns a boolean indicating the success status */
int socket_read_block (int sock, char *dest, int len)
{
    int nread, n;

    for (nread = 0; nread < len;){
	n = recv (sock, dest+nread, len-nread, 0);
	if (n <= 0){
	    tcp_invalidate_socket (sock);
	    return 0;
	}
	nread += n;
    }
    return 1;
}

int socket_write_block (int sock, char *buffer, int len)
{
    int left, status;

    for (left = len; left > 0;){
	status = send (sock, buffer, left, 0);
	CHECK_SIG_PIPE (sock);
	if (status < 0)
	    return 0;
	left -= status;
	buffer += status;
    }
    return 1;
}

int send_string (int sock, char *string)
{
    return socket_write_block (sock, string, strlen (string));
}

int rpc_send (int sock, ...)
{
    long int tmp, len, cmd;
    char *text;
    va_list ap;

    va_start (ap, sock);

    for (;;){
	cmd = va_arg (ap, int);
	switch (cmd){
	case RPC_END:
	    va_end (ap);
	    return 1;

	case RPC_INT:
	    tmp = htonl (va_arg (ap, int));
	    send (sock, &tmp, sizeof (tmp), 0);
	    CHECK_SIG_PIPE (sock);
	    break;

	case RPC_STRING:
	    text = va_arg (ap, char *);
	    len = strlen (text);
	    tmp = htonl (len);
	    send (sock, &tmp, sizeof (tmp), 0);
	    CHECK_SIG_PIPE (sock);
	    send (sock, text, len, 0);
	    CHECK_SIG_PIPE (sock);
	    break;	    

	case RPC_BLOCK:
	    len = va_arg (ap, int);
	    text = va_arg (ap, char *);
	    tmp = htonl (len);
	    send (sock, text, len, 0);
	    CHECK_SIG_PIPE (sock);
	    break;

	default:
	    fprintf (stderr, "Unknown rpc message\n");
	    abort ();
	}
    }
}

typedef struct sock_callback_t {
    int  sock;
    void (*cback)(int);
    struct sock_callback_t *link;
} sock_callback_t;

sock_callback_t *sock_callbacks = 0;

static void check_hooks (int sock)
{
    sock_callback_t *callback, *prev;

    for (prev=callback = sock_callbacks; callback; callback = callback->link){
	if (callback->sock != sock){
	    prev = callback;
	    continue;
	}
	callback->sock = -1;
	(callback->cback)(sock);
	if (callback == sock_callbacks){
	    sock_callbacks = callback->link;
	} else {
	    prev->link = callback->link;
	}
	free (callback);
	return;
    }
}

int rpc_get (int sock, ...)
{
    long int tmp, len;
    char *text, **str_dest;
    int  *dest, cmd;
    va_list ap;

    va_start (ap, sock);

    check_hooks (sock);

    for (;;){
	cmd = va_arg (ap, int);
	switch (cmd){
	case RPC_END:
	    va_end (ap);
	    return 1;

	case RPC_INT:
	    if (socket_read_block (sock, (char *) &tmp, sizeof (tmp)) == 0)
		return 0;
	    dest = va_arg (ap, int *);
	    *dest = ntohl (tmp);
	    break;

	    /* returns an allocated string */
	case RPC_LIMITED_STRING:
	case RPC_STRING:
	    if (socket_read_block (sock, (char *)&tmp, sizeof (tmp)) == 0)
		return 0;
	    len = ntohl (tmp);
	    if (cmd == RPC_LIMITED_STRING)
		if (len > 16*1024){
		    /* silently die */
		    abort ();
		}
	    if (len > 128*1024)
		    abort ();
	    text = malloc (len+1);
	    if (socket_read_block (sock, text, len) == 0)
		return 0;
	    str_dest = va_arg (ap, char **);
	    *str_dest = text;
	    text [len] = 0;
	    break;	    

	case RPC_BLOCK:
	    len = va_arg (ap, int);
	    text = va_arg (ap, char *);
	    if (socket_read_block (sock, text, len) == 0)
		return 0;
	    break;

	default:
	    fprintf (stderr, "Unknown rpc message\n");
	    abort ();
	}
    }
}

void rpc_add_get_callback (int sock, void (*cback)(int))
{
    sock_callback_t *new;

    new = malloc (sizeof (sock_callback_t));
    new->cback = cback;
    new->sock = sock;
    new->link = sock_callbacks;
    sock_callbacks = new;
}

#if 0

#if defined(IS_AIX) || defined(linux) || defined(SCO_FLAVOR) || defined(__QNX__)
static void sig_pipe (int unused)
#else
static void sig_pipe (void)
#endif
{
    got_sigpipe = 1;
}

#else

#if (RETSIGTYPE==void)
#define handler_return return
#else
#define handler_return return 1
#endif
RETSIGTYPE sig_pipe (int unused)
{
    got_sigpipe = 1;
    handler_return;
}

#endif

void tcp_init (void)
{
#if 0
    struct sigaction sa;
    
    got_sigpipe = 0;
    sa.sa_handler = sig_pipe;
    sa.sa_flags = 0;
    sigemptyset (&sa.sa_mask);
    sigaction (SIGPIPE, &sa, NULL);
#else
    got_sigpipe = 0;
#ifdef SIGPIPE
    signal (SIGPIPE, sig_pipe);
#endif
#endif
}

int get_remote_port (struct sockaddr_in *si, int *version)
{
#ifdef HAVE_PMAP_GETMAPS
    int              port;
    struct pmaplist  *pl;
    
    *version = 1;
    port     = mcserver_port;
    for (pl = pmap_getmaps (si); pl; pl = pl->pml_next)
	if (pl->pml_map.pm_prog == RPC_PROGNUM &&
	    pl->pml_map.pm_prot == IPPROTO_TCP &&
	    pl->pml_map.pm_vers >= *version) {
	    *version = pl->pml_map.pm_vers;
	    port     = pl->pml_map.pm_port;
	}
    return port;
#else
#ifdef HAVE_PMAP_GETPORT
    int              port;
    for (*version = RPC_PROGVER; *version >= 1; (*version)--)
	if (port = pmap_getport (si, RPC_PROGNUM, *version, IPPROTO_TCP))
	    return port;
#endif /* HAVE_PMAP_GETPORT */
#endif /* HAVE_PMAP_GETMAPS */
    *version = 2;	/* FIXED: version defaults to 2 now */
    return mcserver_port;
}


/* {{{ oob handler */

#undef BUFFER_SIZE
#define BUFFER_SIZE 8192

static int oob_remote_sock;
static int oob_local_sock;

#ifdef SIGURG
RETSIGTYPE handle_oob_data (int unused)
{
    char local_buf[BUFFER_SIZE], remote_buf[BUFFER_SIZE];
    int cl, cr;
    cr = recv (oob_local_sock, remote_buf, BUFFER_SIZE, MSG_OOB);
    cl = recv (oob_remote_sock, local_buf, BUFFER_SIZE, MSG_OOB);
    if (cl > 0)
	send (oob_local_sock, local_buf, cl, MSG_OOB);
    if (cr > 0)
	send (oob_remote_sock, remote_buf, cr, MSG_OOB);
    signal (SIGURG, handle_oob_data);
    handler_return;
}
#endif

void set_oob_function (local_sock, remote_sock)
{
    oob_remote_sock = remote_sock;
    oob_local_sock = local_sock;
#ifdef SIGURG
    signal (SIGURG, handle_oob_data);
#endif
}

/* }}} oob handler */

/* {{{ redirector */

#define WOULD_BLOCK(c) { if ((c) < 0 && errno == EWOULDBLOCK) (c) = 0; if ((c) < 0) return; }
#undef MAX
#define MAX(x,y) ((x) > (y) ? (x) : (y))

#if 0
#define debug(x,y,d) printf ("%s: %s: %d\n", (x), (y), (d))
#else
#define debug(x,y,d) 
#endif

void redirect (int local_sock, int remote_sock)
{
    char remote_buf[BUFFER_SIZE], local_buf[BUFFER_SIZE];
    int remote_avail = 0, local_avail = 0;
    int remote_written = 0, local_written = 0;
    set_oob_function (local_sock, remote_sock);

    for (;;) {
	int n = 0, r;
	fd_set writing, reading;
	FD_ZERO (&writing);
	FD_ZERO (&reading);
/* is there stuff waiting to be written to local ? */
	if (remote_avail > remote_written) {
	    FD_SET (local_sock, &writing);
	    n = MAX (local_sock, n);
	}
/* is there stuff waiting to be written to remote ? */
	if (local_avail > local_written) {
	    FD_SET (remote_sock, &writing);
	    n = MAX (remote_sock, n);
	}
/* is there buffer space to read from local ? */
	if (local_avail < BUFFER_SIZE) {
	    FD_SET (local_sock, &reading);
	    n = MAX (local_sock, n);
	}
/* is there buffer space to read from remote ? */
	if (remote_avail < BUFFER_SIZE) {
	    FD_SET (remote_sock, &reading);
	    n = MAX (remote_sock, n);
	}
	debug ("select", "", n);
	do {
	    r = select (n + 1, &reading, &writing, NULL, NULL);
	} while (r == -1 && errno == EINTR);
	if (r == -1)
	    break;
	debug ("select", "done", 0);
	if (FD_ISSET (local_sock, &reading)) {
	    int c;
	    debug ("reading", "local", BUFFER_SIZE - local_avail);
	    c = recv (local_sock, local_buf + local_avail, BUFFER_SIZE - local_avail, 0);
	    if (!c)
		return;
	    debug ("read   ", "local", c);
	    WOULD_BLOCK (c);
	    local_avail += c;
	    if (c)
		goto try_write_remote;
	}
	if (FD_ISSET (remote_sock, &writing)) {
	    int c;
	  try_write_remote:
	    debug ("writing", "remote", local_avail - local_written);
	    c = send (remote_sock, local_buf + local_written, local_avail - local_written, 0);
	    debug ("written", "remote", c);
	    WOULD_BLOCK (c);
	    local_written += c;
	    if (local_written == local_avail)
		local_avail = local_written = 0;
	}
	if (FD_ISSET (remote_sock, &reading)) {
	    int c;
	    debug ("reading", "remote", BUFFER_SIZE - remote_avail);
	    c = recv (remote_sock, remote_buf + remote_avail, BUFFER_SIZE - remote_avail, 0);
	    if (!c)
		return;
	    debug ("read   ", "remote", c);
	    WOULD_BLOCK (c);
	    remote_avail += c;
	    if (c)
		goto try_write_local;
	}
	if (FD_ISSET (local_sock, &writing)) {
	    int c;
	  try_write_local:
	    debug ("writing", "remote", remote_avail - remote_written);
	    c = send (local_sock, remote_buf + remote_written, remote_avail - remote_written, 0);
	    debug ("written", "remote", c);
	    WOULD_BLOCK (c);
	    remote_written += c;
	    if (remote_written == remote_avail)
		remote_avail = remote_written = 0;
	}
    }
}

/* }}} redirector */

