#include <stdio.h>
#include <stdlib.h>
#include <string.h>    /* for memset */
#include <ctype.h>
#include <signal.h>
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> /* for inet_ntoa */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <termios.h>
#include <fcntl.h>

/* This file is part of Bisqwit's pipes and remotegcc packages. */

static int isinvalidport(const char *s)
{
    char *r;
    strtol(s, &r, 10);    
    return *r ? 1 : 0;
}

static struct termios tattr, saved_attributes;
static void fixmodes(void)
{
    setvbuf(stdout, NULL, _IONBF, 0);

    tcgetattr(0, &saved_attributes);
    tcgetattr(0, &tattr);
    tattr.c_lflag &= ~ICANON;
    tcsetattr(0, TCSAFLUSH, &tattr);
}
static void restoremodes(void)
{
    tcsetattr(0, TCSAFLUSH, &saved_attributes);
}
extern int main(int argc, const char *const *argv);
static void ChildPconTest(const char *cmdline)
{
    int a, c, spa=0, spos;
    char **newargs;
    if(strchr(cmdline, '"')
    || strchr(cmdline, '\'')
    || strchr(cmdline, '`')
    || strchr(cmdline, '$')
    || strchr(cmdline, '\\'))
        return;
    c=0;
    spa=1;
    /* Count the parameters */
    for(a=0; cmdline[a]; ++a)
        if(isspace(cmdline[a]) != spa)
        {
            spa = isspace(cmdline[a]);
            if(!spa)++c;
        }
    newargs = (char **)malloc((c+1) * sizeof(char *));
    newargs[c] = NULL;
    /* Do the parameters */
    c=0;
    spa=1;
    spos=0;
    for(a=0; ; ++a)
    {
        if(!cmdline[a]) { spa=1; goto Flush; }
        if(isspace(cmdline[a]) != spa)
        {
            spa = isspace(cmdline[a]);
            if(!spa)spos = a;
            else
            {
        Flush:
                newargs[c] = (char *)malloc(a-spos+1);
                strncpy(newargs[c], cmdline+spos, a-spos);
                newargs[c][a-spos] = 0;
                
                /* debug: write(2, newargs[c], a-spos); */
                
                ++c;
            }
        }
        if(!cmdline[a])break;
    }
    a = main(c, (const char *const *) newargs);
    while(c > 0) free(newargs[--c]);
    free(newargs);
    exit(a);
}

#include "cat.h"
int main(int argc, const char *const *argv)
{
    int port, fix=0, MyPort=0, verbose=0, usesh=1;
    int Plug, Sock;
    unsigned AddrLen;
    const char *a0 = argv[0], *q;
    const char *connhost = NULL;
    const char *bindhost = NULL;
    char *currentwd = NULL;
    struct sockaddr_in MyAddr, They;
    const char *SHELL = getenv("SHELL");
    if(!SHELL)SHELL = "/bin/sh";

    /* signal(SIGCHLD, SIG_IGN); - ignored by default */
    signal(SIGPIPE, SIG_IGN); /* Would terminate the program. Not wanted. */
    
    if(!strcmp(*argv, "cat"))cat();
    
    while((q = strchr(a0, '/')))a0 = q+1;
    if(argc <= 1)
    {
Usage:
#ifdef main
        printf("Syntax error\n");
#else
        printf(
            "Pipes version "VERSION"\n"
            "Copyright (C) 1992,2003 Bisqwit (http://iki.fi/bisqwit/)\n\n"
            "Usage: %s [-s] [-f] [-v] [-h<bindip>] %s<port> [-ioe <command>] [...]\n"
            "-f = fix console modes first... (termios)\n"
            "-v = verbose (stderr)\n"
            "-s = do not use sh -c, do not even give argv[0]\n"
            "-h = bind connection to a local interface\n",
            a0, strcmp(a0, "plis") ? "[-p<myport>] <host> " : ""
         );
         if(strcmp(a0, "plis"))
             printf(
            "-p = bind connection to a local port\n");
         printf(
            "Use capital switch (-I, -O, -E) to fcntl the pipe nonblocking.\n"
            "Read the Examples file for examples of usage.\n");
#endif
        return -1;
    }
    
    for(;;)
    {
        if(!strcmp(argv[1], "-s")) { --argc, ++argv, usesh=0; continue; }
        if(!strcmp(argv[1], "-f")) { --argc, ++argv, fix=1; continue; }
        if(!strcmp(argv[1], "-v")) { --argc, ++argv, ++verbose; continue; }
        break;
    }
    
    if(argc > 1)
        if(argv[1][0]=='-' && argv[1][1]=='h')
        {
            --argc;
            bindhost = *++argv + 2;
        }
    
    if(isinvalidport(argv[1]))
    {
        if(argv[1][0]=='-' && argv[1][1]=='p')
            --argc, MyPort = atoi(*++argv + 2);
        connhost = (--argc, *++argv);
        if(!connhost || argc<=1)goto Usage;
    }
        
    port = (--argc, atoi(*++argv));
    
    if(1)
    {
        /* Close the fd's that will always be redirected */
        
        int a;
        struct stat st1, st2;
        int i=1,i1=0, o=1,o1=0, e=1,e1=0;
        for(a=1; a<argc; a++)
        {
            const char *s = argv[a];
            if(*s=='-')
            {
                int I=0,O=0,E=0;
                while(*++s)
                    switch(*s)
                    {
                        case 'i': case 'I': I=i1=1; break;
                        case 'o': case 'O': O=o1=1; break;
                        case 'e': case 'E': E=e1=1; break;
                    }
                ++a;
                if(!I)i=0;
                if(!O)o=0;
                if(!E)e=0;
            }
        }
        /* Jos kaikki on redirektoitu aina, ei tarkisteta stattia.
         * Jos statti onnistuu, redirektoinneilla ei ole merkitystä.
         * Muutoin jotain pitää olla redirektoitu.
         */
        #if 1
        if((i && o && e) || !(a = stat("/dev/null", &st1)) || (i || o || e))
        {
            /* Jos fd redirektoidaan *aina*, se suljetaan huolimatta seuraavasta. */
            /* Jos statti epäonnistui tai se fd redirektoidaan joskus
             * tai fd:n statti epäonnistui tai fd ei ole ohjattu /dev/nulliin,
             * sitä ei suljeta, muuten suljetaan. */
            if(i || !(a || i1 || fstat(0, &st2) || st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev)) { fclose(stdin); }
            if(o || !(a || o1 || fstat(1, &st2) || st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev)) { fclose(stdout); }
            if(e || !(a || e1 || fstat(2, &st2) || st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev)) { fclose(stderr); }
        }
        #endif
    }
    
    currentwd = getcwd(NULL, 0);
    if(currentwd)
    {
        /* Unreserve the current working directory */
        chdir("/");
    }
    
    Plug = socket(AF_INET, SOCK_STREAM, 0);
    if(Plug < 0) { perror("socket"); return -errno; }
    
    if(fix)fixmodes();
    
    if(connhost)
    {
        struct hostent *HostName = gethostbyname(connhost);
        if(!HostName) { perror("gethostbyname"); return -errno; }
        
        if(MyPort > 0)
        {
            /* If connecting from a specific port */
        ReBind1:
            MyAddr.sin_port = htons(MyPort);            /* network order */
            MyAddr.sin_family = AF_INET;                /* host byte order */
            MyAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* auto-fill with my IP, network long  */
            memset((void *)&(MyAddr.sin_zero), 0, 8);   /* zero the rest of the struct */
            
            if(bindhost)
            {
                struct hostent *HN = gethostbyname(bindhost);
                if(!HN) { perror("gethostbyname"); return -errno; }
                memcpy(&MyAddr.sin_addr, HN->h_addr, HN->h_length);
            }
            
            Sock=1; /* Just a temp variable, boolean value for setsockopt() */
            setsockopt(Plug, SOL_SOCKET, SO_REUSEADDR, &Sock, sizeof Sock);
            
            if(bind(Plug, (struct sockaddr *)&MyAddr, sizeof(struct sockaddr)))
            {
                if(errno == EADDRINUSE)    /* Address already in use */
                {
                    printf("Port %d busy, sleeping 5 seconds and retrying\n", MyPort);
                    sleep(5);
                    goto ReBind1;
                }
                perror("bind");
                return -errno;
            }
        }

        memset(&MyAddr, 0, sizeof(MyAddr));
        
        MyAddr.sin_family = AF_INET;
        MyAddr.sin_port = htons(port);
        memcpy(&MyAddr.sin_addr, HostName->h_addr, HostName->h_length);
        for(;;)
        {
            struct pollfd tmp = {0, POLLOUT, 0};
            int a;
            socklen_t len;
            
            if(verbose)
            {
                fprintf(stderr, "Connecting to %s port %d", connhost, port);
                if(MyPort > 0)fprintf(stderr, " (from port %d)", MyPort);
                fprintf(stderr, "...\n");
            }
            fcntl(Plug, F_SETFL, fcntl(Plug, F_GETFL) | O_NONBLOCK);
            if(!connect(Plug, (struct sockaddr *)&MyAddr, sizeof(MyAddr)))
            {
                /* Success without delay! (can this really happen?) */
                break;
            }
            
RePoll:
            tmp.fd = Plug;
            tmp.events = POLLOUT;
            tmp.revents = 0;
            
            if(!poll(&tmp, 1, 10000))
            {
                /* Timeout */
                errno = ETIMEDOUT;
                perror("connect");
                return -1;
            }
            
            len = sizeof(a);
            if(getsockopt(Plug, SOL_SOCKET, SO_ERROR, &a, &len) < 0)
            {
                perror("getsockopt");
                return -1;
            }
            
            if(a == EINTR)
            {
                goto RePoll;
            }
            
            if(!a)
            {
                fcntl(Plug, F_SETFL, fcntl(Plug, F_GETFL) &~ O_NONBLOCK);
            
                /* Success */
                break;
            }
            
            errno = a;
            perror("connect");
            return -1;
        }
        Sock = Plug;
        goto Diu;
    }

ReBind:
    MyAddr.sin_port = htons(port);              /* network order */
    MyAddr.sin_family = AF_INET;                /* host byte order */
    MyAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* auto-fill with my IP, network long  */
    memset((void *)&(MyAddr.sin_zero), 0, 8);   /* zero the rest of the struct */

    if(bindhost)
    {
        struct hostent *HN = gethostbyname(bindhost);
        if(!HN) { perror("gethostbyname"); return -errno; }
        memcpy(&MyAddr.sin_addr, HN->h_addr, HN->h_length);
    }
    
    Sock=1; /* Just a temp variable, boolean value for setsockopt() */
    setsockopt(Plug, SOL_SOCKET, SO_REUSEADDR, &Sock, sizeof Sock);
    
    if(bind(Plug, (struct sockaddr *)&MyAddr, sizeof(struct sockaddr)))
    {
        if(errno == EADDRINUSE)    /* Address already in use */
        {
            printf("Port %d busy, sleeping 5 seconds and retrying\n", port);
            sleep(5);
            goto ReBind;
        }
        perror("bind");
        return -errno;
    }
    
    if(listen(Plug, 10))
    {
        perror("listen");
        return -errno;
    }
    
    AddrLen = sizeof(They);
    for(;;)
    {
        int forkpid;
        
        Sock = accept(Plug, (struct sockaddr *)&They, &AddrLen);
        if(Sock < 0)
        {
            perror("accept");
            sleep(1);
            continue;
        }
        if(verbose)
        {
            fprintf(stderr, "Got connection, from port %d", They.sin_port);
            fprintf(stderr, ", host %s\n", inet_ntoa(They.sin_addr));
        }
        
        /* Finish dead children here. Don't hang waiting. */
        waitpid(-1, NULL, WNOHANG);
        
        /* Make a new subprocess to handle the new connection. */
        forkpid = fork();
        switch(forkpid)
        {
            case -1:
                perror("fork");
                break;
            case 0:
            {
                int a;
                struct childpid { int pid; struct childpid *next; } *childpids;
                
                /* The child process does not need the listening plug. */
                close(Plug);

Diu:            /* At this point the connection is established. */
                /* Sock holds the fd (the only copy)            */
                
                childpids = NULL;
                
                for(a=1; a<argc; a++)
                {
                    const char *s = argv[a];
                    if(*s=='-')
                    {
                        int i=0, o=0, e=0, b;
                        while(*++s)
                            switch(*s)
                            {
                                case 'i': i=1; break;
                                case 'o': o=1; break;
                                case 'e': e=1; break;
                                case 'I': i=2; break;
                                case 'O': o=2; break;
                                case 'E': e=2; break;
                            }
                        ++a;
                        
                        /* Make a new subprocess.
                         * The same connection may use multiple programs
                         * to handle different fd's.
                         */
                        b = fork();
                        if(b < 0)
                            perror("fork");
                        else if(!b)
                        {
                            int unused=1;
                            if(i) { if(Sock==0)unused=0; else dup2(Sock, 0); }
                            if(o) { if(Sock==1)unused=0; else dup2(Sock, 1); }
                            if(e) { if(Sock==2)unused=0; else dup2(Sock, 2); }
                            if(i==2) fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
                            if(o==2) fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);
                            if(e==2) fcntl(2, F_SETFL, fcntl(2, F_GETFL) | O_NONBLOCK);
                            if(unused)close(Sock);
                            
                            if(!strcmp(argv[a], "cat")) cat();
                            
                            if(currentwd)
                            {
                                /* chdir to the directory this program was started in. */
                                chdir(currentwd);
                            }
                            
                            if(!strncmp(argv[a], "pcon ", 5)) ChildPconTest(argv[a]);
                            
                            if(usesh)
                                execl(SHELL, "sh", "-c", argv[a], NULL);
                            else
                                execlp(argv[a], NULL);
                        }
                        else
                        {
                            struct childpid *tmp = (struct childpid*)malloc(sizeof(*tmp));
                            tmp->pid = b;
                            tmp->next = childpids;
                            childpids = tmp;
                        }
                    }
                }
                
                /* Don't reserve NFS-directories while waiting for children */
                if(!currentwd)
                    chdir("/");
                
                /* We don't need socket this anymore.
                 * All copies are on the children's resposibility. */
                close(Sock);
                
                for(;;)
                {
                    /* Wait for children. */
                    struct childpid *tmp;
                    int b = wait(NULL);
                    if(b <= 0) break;
                    
                    /* A child has died. Kill all children. */
                    tmp = childpids;

                    for(; tmp; tmp = tmp->next)
                        kill(tmp->pid, SIGTERM);
                }
                
                if(connhost)
                {
                    /* If this is a foreground process,
                     * then the exit must be done properly.
                     */
                    goto Quit;
                }
                
                /* This subprocess would not go to the listening loop. */
                _exit(0);
            }
            
            default:
                /* When listening, we have a copy here in the parent task. */
                /* Close the copy.                                         */
                close(Sock);
        }
        /* The subprocess runs happily at background. */
        /* Nobody waits for her, but that doesn't matter. */
        /* One zombie doesn't hurt anybody. */
    }
Quit:
    if(fix)restoremodes();
    return 0;
}
