/*      flop2.c
 *
 * Flop 2
 *
 * Copyright 1997 Petteri Kangaslampi
*/

/* TODO: poista hakemisto nimestä (ettei tule /etc/passwd) */

#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER)
#   define __WIN32__
#endif

#ifdef __WIN32__
#   define WIN32_LEAN_AND_MEAN
#   include <windows.h>
#   include <winsock.h>
#   include <io.h>
#   include <fcntl.h>
#else
#   include <unistd.h>
#   include <sys/types.h>
#   include <sys/socket.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#   include <fcntl.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

/* WARNING! Assumes 32-bit integers! */

#define BUFFERSIZE 65536

#ifndef __WIN32__
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#define closesocket close
#endif

#define VERBOSE(x) { if ( verbose ) x; }

SOCKET          sock;

FILE            *f = NULL;
char            *buffer;
int verbose = 0;

char *usage = "flop [options] command [host:]port [files]\n"
"\n"
"options:\n"
"        -v      verbose\n"
"\n"
"commands:\n"
"        g       get files (active)\n"
"        G       get to stdout (active)\n"
"        o       offer files (passive)\n"
"        O       offer from stdin (passive)\n"
"        r       receive files\n"
"        R       receive to stdout\n"
"        s       send files\n"
"        S       send from stdin\n";


void Error(char *msg)
{
    fprintf(stderr, "flop: %s\n", msg);

    if ( f != NULL )
        fclose(f);

#ifdef __WIN32__
    WSACleanup();
#endif

    exit(EXIT_FAILURE);
}


int KoolSend(SOCKET s, unsigned char *buf, int len, int flags)
{
    int         sent;

    while ( len )
    {
        sent = send(s, buf, len, flags);

        if ( (sent == 0) || (sent == SOCKET_ERROR) )
            return 0;

        buf += sent;
        len -= sent;
    }

    return 1;
}



int KoolRecv(SOCKET s, unsigned char *buf, int len, int flags)
{
    int         recvd;

    while ( len )
    {
        recvd = recv(s, buf, len, flags);

        if ( (recvd == 0) || (recvd == SOCKET_ERROR) )
            return 0;

        buf += recvd;
        len -= recvd;
    }

    return 1;
}


#if 0

void Send(char *fileName, char *hostname, int port)
{
    struct sockaddr_in serv_addr;
    struct hostent *hostptr;
    unsigned    fileLength;
    unsigned    netLength;
    unsigned char nlen;
    unsigned    fileLeft, doNow;

    if ( strlen(fileName) > 255 )
        Error("File name too long");

    /* Open the file: */
    if ( (f = fopen(fileName, "rb")) == NULL )
        Error("Unable to open file");

    /* Get file size: */
    fseek(f, 0, SEEK_END);
    fileLength = (unsigned) ftell(f);
    fseek(f, 0, SEEK_SET);

    VERBOSE(fprintf(stderr, "Connecting...\n"));

    if( (hostptr = gethostbyname( hostname )) == 0 )
        Error("DNS lookup failure");

    if ( (sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
        Error("Unable to open socket");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr = *(struct in_addr*)(hostptr->h_addr);
    serv_addr.sin_port = htons(port);
    if ( connect(sock, (struct sockaddr*) &serv_addr,
        sizeof(serv_addr)) != 0 )
        Error("connect() failed");

    VERBOSE(fprintf(stderr, "Connected!\n"));

    /* Send file length: */
    netLength = htonl(fileLength);
    if ( !KoolSend(sock, (unsigned char*) &netLength, 4, 0) )
        Error("send() failed");

    /* Send name length: */
    nlen = strlen(fileName);
    if ( !KoolSend(sock, &nlen, 1, 0) )
        Error("send() failed");

    /* Send name: */
    if ( !KoolSend(sock, fileName, nlen, 0) )
        Error("send() failed");

    /* Send file: */
    fileLeft = fileLength;
    while ( fileLeft )
    {
        printf("%s: %-10u\r", fileName, fileLeft);

        if ( fileLeft > BUFFERSIZE )
            doNow = BUFFERSIZE;
        else
            doNow = fileLeft;

        fread(buffer, doNow, 1, f);
        if ( !KoolSend(sock, buffer, doNow, 0) )
            Error("send() failed");
        fileLeft -= doNow;
    }

    printf("%s: %-10u\r", fileName, fileLeft);

    fclose(f);
    closesocket(sock);
}



void Receive(int port)
{
    struct sockaddr_in addr;
    unsigned    fileLength;
    unsigned    netLength;
    unsigned char nlen;
    char        *fileName;
    unsigned    fileLeft, doNow;
    SOCKET      listenSock;
    int         addrlen;

    printf("Waiting for connection...\n");

    if ( (listenSock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
        Error("Unable to open socket");

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);

    if ( bind(listenSock, (struct sockaddr*) &addr, sizeof(addr)) != 0 )
        Error("bind() failed");

    if ( listen(listenSock, 1) )
        Error("listen() failed");

    addrlen = sizeof(addr);
    if ( (sock = accept(listenSock, (struct sockaddr*) &addr, &addrlen))
        == INVALID_SOCKET )
        Error("accept() failed");

    printf("Connected!\n");

    /* Get file length: */
    if ( !KoolRecv(sock, (unsigned char*) &netLength, 4, 0) )
        Error("recv() failed");
    fileLength = ntohl(netLength);

    /* Get name length: */
    if ( !KoolRecv(sock, &nlen, 1, 0) )
        Error("recv() failed");

    if ( (fileName = malloc(((unsigned)nlen + 1))) == NULL )
        Error("Out of memory");

    /* Get name: */
    if ( !KoolRecv(sock, fileName, nlen, 0) )
        Error("recv() failed");
    fileName[nlen] = 0;

    /* Open da file: */
    if ( (f = fopen(fileName, "wb")) == NULL )
        Error("Unable to open output file");

    /* Get file: */
    fileLeft = fileLength;
    while ( fileLeft )
    {
        printf("%s: %-10u\r", fileName, fileLeft);

        if ( fileLeft > BUFFERSIZE )
            doNow = BUFFERSIZE;
        else
            doNow = fileLeft;

        if ( !KoolRecv(sock, buffer, doNow, 0) )
            Error("recv() failed");
        fwrite(buffer, doNow, 1, f);

        fileLeft -= doNow;
    }

    printf("%s: %-10u\r", fileName, fileLeft);

    fclose(f);
    closesocket(sock);
}

#endif


void Get(int tostdout)
{
    tostdout = tostdout;
}


void Receive(int tostdout, int port)
{
    struct sockaddr_in addr;
    unsigned    fileLength;
    unsigned    netLength;
    unsigned char nlen;
    char        *fileName;
    unsigned    fileLeft, doNow;
    SOCKET      listenSock;
    int         addrlen;
    int         gotBytes;

    VERBOSE(fprintf(stderr, "Waiting for connection...\n"));

    if ( (listenSock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
        Error("Unable to open socket");

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);

    if ( bind(listenSock, (struct sockaddr*) &addr, sizeof(addr)) != 0 )
        Error("bind() failed");

    if ( listen(listenSock, 1) )
        Error("listen() failed");

    addrlen = sizeof(addr);
    if ( (sock = accept(listenSock, (struct sockaddr*) &addr, &addrlen))
        == INVALID_SOCKET )
        Error("accept() failed");

    VERBOSE(fprintf(stderr, "Connected!\n"));

    /* Try to get the length of the next file, and loop as long as new ones
       are available: */
    while ( KoolRecv(sock, (unsigned char*) &netLength, 4, 0) )
    {
        fileLength = ntohl(netLength);

        /* Get name length: */
        if ( !KoolRecv(sock, &nlen, 1, 0) )
            Error("recv() failed");        

        if ( (fileName = malloc(((unsigned)nlen + 1))) == NULL )
            Error("Out of memory");

        /* Get name: */
        if ( !KoolRecv(sock, fileName, nlen, 0) )
            Error("recv() failed");
        fileName[nlen] = 0;

        /* Open da file: */
        if ( tostdout )
        {
            f = stdout;
        }
        else
        {
            if ( (f = fopen(fileName, "wb")) == NULL )
                Error("Unable to open output file");
        }

        /* Get file: */
        if ( fileLength )
            fileLeft = fileLength;
        else
            fileLeft = UINT_MAX;
        
        while ( fileLeft )
        {
            if ( (fileLeft != UINT_MAX) && (!tostdout) )
                VERBOSE(fprintf(stderr, "%s: %-10u\r", fileName, fileLeft));

            if ( fileLeft > BUFFERSIZE )
                doNow = BUFFERSIZE;
            else
                doNow = fileLeft;

            gotBytes = recv(sock, buffer, doNow, 0);
            if ( gotBytes <= 0 )
                break;
            doNow = (unsigned) gotBytes;
            fwrite(buffer, doNow, 1, f);

            if ( fileLeft != UINT_MAX )
                fileLeft -= doNow;
        }

        if ( (fileLeft != UINT_MAX) && (!tostdout) )
            VERBOSE(fprintf(stderr, "%s: %-10u\n", fileName, fileLeft));

        if ( !tostdout )
            fclose(f);
    }

    closesocket(sock);
}



void SendFile(FILE *file, char *name, unsigned length, SOCKET sock)
{
    unsigned    netLength;
    unsigned char nlen;
    unsigned    fileLeft, doNow;

    if ( strlen(name) > 255 )
        Error("File name too long");

    /* Send file length: */
    netLength = htonl(length);
    if ( !KoolSend(sock, (unsigned char*) &netLength, 4, 0) )
        Error("send() failed");

    /* Send name length: */
    nlen = strlen(name);
    if ( !KoolSend(sock, &nlen, 1, 0) )
        Error("send() failed");

    /* Send name: */
    if ( !KoolSend(sock, name, nlen, 0) )
        Error("send() failed");

    /* Send file: */
    if ( length )
        fileLeft = length;
    else
        fileLeft = UINT_MAX;

    while ( fileLeft && (!feof(file)) && (!ferror(file)) )
    {
        if ( fileLeft != UINT_MAX )
            VERBOSE(fprintf(stderr, "%s: %-10u\r", name, fileLeft));

        if ( fileLeft > BUFFERSIZE )
            doNow = BUFFERSIZE;
        else
            doNow = fileLeft;

        doNow = fread(buffer, 1, doNow, file);
        if ( !KoolSend(sock, buffer, doNow, 0) )
            Error("send() failed");

        if ( fileLeft != UINT_MAX )
            fileLeft -= doNow;
    }

    if ( fileLeft != UINT_MAX )
        VERBOSE(fprintf(stderr, "%s: %-10u\r", name, fileLeft));
}



void SendStdin(char *hostName, int port)
{
    struct sockaddr_in serv_addr;
    struct hostent *hostptr;

    VERBOSE(fprintf(stderr, "Connecting...\n"));

    if( (hostptr = gethostbyname(hostName)) == 0 )
        Error("DNS lookup failure");

    if ( (sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
        Error("Unable to open socket");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr = *(struct in_addr*)(hostptr->h_addr);
    serv_addr.sin_port = htons(port);
    if ( connect(sock, (struct sockaddr*) &serv_addr,
        sizeof(serv_addr)) != 0 )
        Error("connect() failed");

    VERBOSE(fprintf(stderr, "Connected!\n"));

    /* Send it: */
    SendFile(stdin, "flop.stdin", 0, sock);
        
    closesocket(sock);
}


void SendFiles(int numNames, char **names, char *hostName, int port)
{
    struct sockaddr_in serv_addr;
    struct hostent *hostptr;
    unsigned    fileLength;

    VERBOSE(fprintf(stderr, "Connecting...\n"));

    if( (hostptr = gethostbyname(hostName)) == 0 )
        Error("DNS lookup failure");

    if ( (sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
        Error("Unable to open socket");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr = *(struct in_addr*)(hostptr->h_addr);
    serv_addr.sin_port = htons(port);
    if ( connect(sock, (struct sockaddr*) &serv_addr,
        sizeof(serv_addr)) != 0 )
        Error("connect() failed");

    VERBOSE(fprintf(stderr, "Connected!\n"));

    while ( numNames )
    {
        if ( strlen(*names) > 255 )
            Error("File name too long");
        
        /* Open the file: */
        if ( (f = fopen(*names, "rb")) == NULL )
            Error("Unable to open file");

        /* Get file size: */
        fseek(f, 0, SEEK_END);
        fileLength = (unsigned) ftell(f);
        fseek(f, 0, SEEK_SET);

        /* Send it: */
        SendFile(f, *names, fileLength, sock);
        
        fclose(f);

        names++;
        numNames--;
    }
    
    closesocket(sock);
}


void OfferStdin(void)
{
}



void OfferFiles(int numNames, char **names)
{
}


extern void wildExpand(int *argc, char ***argv);

int main(int argc, char *argv[])
{
    char **arg;
    int port;
    int argLeft;
    char command;
    unsigned hostLen;
    char *hostName, *portStr;
#ifdef __WIN32__
    WSADATA     wsad;
    
    wildExpand(&argc, &argv);
    
    WSAStartup( 0x0101, &wsad );

    setmode(fileno(stdin), O_BINARY);
    setmode(fileno(stdout), O_BINARY);
#endif    

    arg = &argv[1];
    argLeft = argc - 1;
    
    setbuf(stderr, NULL);
    setbuf(stdout, NULL);

    while ( argLeft && ((*arg)[0] == '-') )
    {
        switch ( (*arg)[1] )
        {
            case 'v':
                verbose = 1;
                break;
        }
        arg++;
        argLeft--;
    }

    if ( argLeft < 2 )
    {
        fputs(usage, stderr);
        return EXIT_FAILURE;
    }

    command = *arg[0];
    arg++;
    argLeft--;

    VERBOSE(fprintf(stderr, "arg: \"%s\"\n", *arg));

    if ( strchr(*arg, ':') )
    {
        hostLen = strchr(*arg, ':') - (*arg);
        VERBOSE(fprintf(stderr, "hostLen: %u\n", hostLen));
        hostName = (char*) malloc(hostLen + 1);
        strncpy(hostName, *arg, hostLen);
        hostName[hostLen] = 0;
        portStr = &((*arg)[hostLen+1]);
    }
    else
    {
        hostLen = 0;
        hostName = NULL;
        portStr = *arg;
    }

    port = atoi(portStr);
    if ( port <= 0 )
        Error("Invalid port");
    
    arg++;
    argLeft--;

    if ( (buffer = malloc(BUFFERSIZE)) == NULL )
        Error("Out of memory");

    switch ( command )
    {
        case 'g':
            Get(0);
            break;

        case 'G':
            Get(1);
            break;

        case 'o':
            OfferFiles(argLeft, arg);
            break;

        case 'O':
            OfferStdin();
            break;

        case 'r':
            Receive(0, port);
            break;

        case 'R':
            Receive(1, port);
            break;

        case 's':
            if ( (!hostName) || (strlen(hostName) == 0) )
                Error("Host name required");
            SendFiles(argLeft, arg, hostName, port);
            break;

        case 'S':
            SendStdin(hostName, port);
            break;

        default:
            fputs(usage, stderr);
            return EXIT_FAILURE;
    }

#ifdef __WIN32__
    WSACleanup();
#endif

    free(buffer);

    VERBOSE(fprintf(stderr, "\n"));

    return 0;
}
