//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// 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 3 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <arpa/inet.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include "common.h"
#include "utils.h"
#include "conf.h"
#include "dispatcher.h"
#include "sys_util.h"

extern char tmp[MAXMAXLEN];
extern int  remoteOn;


/* Have issues with sockets if use execNoFork()
   If some app started from anyRemote, then it keeps socket open even if anyRemote
   is killed somehow. Have "ERROR: on binding 98->Address already in use" errors because of
   socket CLOSE_WAIT state reported by /usr/sbin/lsof -i :<port>
   !!! Therefore need to use fork !!!

int execNoFork(const char *cmd)
{
    DEBUG2("execNoFork >%s<", cmd);

    //printf("execute >%s< command\n",cmd);
    #ifdef __cplusplus
    int rc =
    #endif
    system(cmd);

    //printf("command executed %d\n", rc);

    //if (WIFSIGNALED(rc) && (WTERMSIG(rc) == SIGINT || WTERMSIG(rc) == SIGQUIT)) {
    //     	printf("GOR ABORT in system?");
    //}
    //if(rc!=-1 && WIFEXITED(rc)) {
    //	    rc = WEXITSTATUS(rc);
    //}
    //printf("exit from child %d\n", rc);
    return 0;
}*/

static int execInFork(const char *cmd)
{
    if (cmd == NULL) {
        logger(L_ERR,"[EX]: Nothing to fork!");
        return -1;
    }

    //DEBUG2("[EX]: execInFork >%s<", cmd);
    int cpid = fork();
    if (cpid < 0) {
        logger(L_ERR,"[EX]: Can not fork!");
        return -1;
    }

    if (cpid) {      // father
        int rf = 0;
        waitpid(cpid,&rf,0);
        if(rf!=-1 && WIFEXITED(rf)) {
            rf = WEXITSTATUS(rf);
        }
        return rf;
    }

    // else - child

    int rc = 0;
    //logger("INF", "[EX]: Close port in child");

    closePort(0);

    rc = system(cmd);
    if (rc != -1 && WIFEXITED(rc)) {
        rc = WEXITSTATUS(rc);
    }
    exit(rc);
}

int execCmdNoPipe(int subtype, const char *descr, const char *cmd, cmdParams *params)
{
    //logger(L_INF, "[EX]: Command: Exec");

    if (cmd == NULL || strlen(cmd) == 0 || remoteOn != 1) {
        logger(L_DBG, "[EX]: execCmdNoPipe null input or remote if OFF");
        return EXIT_OK;
    }
    DEBUG2("[EX]: execCmdNoPipe >%s<", cmd);

    char* p = (char*) calloc(strlen(cmd) + 2,1);
    if (p == NULL) {
        return EXIT_NOK;
    }
    strcpy(p,cmd);

    // How to handle & inside file names ?
    //if (!index(p, '&') ) {

    int idx = strlen(cmd)-1;
    while (idx > 0 && isspace(cmd[idx])) {
        idx--;
    }

    if (cmd[idx] != '&') {
        strcat(p, "&");
    }

    //int ret = execNoFork(p);
    int ret = execInFork(p);

    free(p);
    return (ret == -1 ? EXIT_NOK : EXIT_OK);
}


#define RSIZE 512

/*
#define READ  0
#define WRITE 1

pid_t popen2(const char *command, int *infp, int *outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0) {
        return -1;
    }
    
    pid = fork();

    if (pid < 0) {
    
        return pid;
	
    } else if (pid == 0) { // child
        
	close(p_stdin[WRITE]);
        dup2(p_stdin[READ], STDIN_FILENO);
	
        close(p_stdout[READ]);
        dup2(p_stdout[WRITE], STDOUT_FILENO);

	execl("/bin/sh", "sh", "-c", command, NULL);
        ERROR2("[EX]: failed in popen2");
        return -1;
    }

    if (infp == NULL) {
        close(p_stdin[WRITE]);
    } else {
        *infp = p_stdin[WRITE];
    }
    
    if (outfp == NULL) {
        close(p_stdout[READ]);
    } else {
        *outfp = p_stdout[READ];
    }
    DEBUG2("[EX]: popen2 >%d<", pid);
    return pid;
}

this version does not send data until script is killed manually

char* executeCommandPipe(const char* exec, size_t* sz)
{
    DEBUG2("[  ]: executeCommandPipe >%s<", exec);

    int outfp;
    pid_t pid = popen2(exec, NULL, &outfp);
    
    fcntl(outfp, F_SETFL, O_NONBLOCK);
    FILE* fp = fdopen(outfp,"r");
    if (fp) {
        setvbuf(fp, NULL, _IONBF, 0);
    }

    char* data = NULL;

    void*  temp[RSIZE];
    size_t haveRead = 0;
    
    int maxWait = getWaitTime();
    time_t start = time(NULL);
    DEBUG2("[  ]: executeCommandPipe WaitTime %d", maxWait);
    
    while (1) {
        
	DEBUG2("[  ]: executeCommandPipe -> read");
	ssize_t num = read(outfp, temp, RSIZE);
	DEBUG2("[  ]: executeCommandPipe <- read %d", num);
	
	if (num == -1 && errno == EAGAIN) {
	    DEBUG2("[  ]: executeCommandPipe EAGAIN");
	    usleep(100000);
	} else if (num > 0) {
            data = realloc(data, haveRead+num+1);
            if (!data) {
                DEBUG2("[  ]: executeCommandPipe error on realloc %zd", haveRead+num);
                return NULL;
            }
            memcpy(data+haveRead, temp, num);
            haveRead += num;
            *(data + haveRead) = '\0';
	} else {
	    DEBUG2("[  ]: executeCommandPipe EOF");
	    break;
	}
	
	if (maxWait > 0) {
	    time_t now = time(NULL); 
	    double elapsed = difftime(now, start);
	    if (elapsed > maxWait) {
	        WARNING2("[  ]: executeCommandPipe wait time exceeded");
		kill(pid, SIGKILL);
		break;
            }
	}
    }
    close(outfp);

    DEBUG2("[  ]: executeCommandPipe got %zd bytes", haveRead);
    (*sz) = haveRead;
    return data;
}*/

/*  this version  also gets blocked if command runs forever

char* executeCommandPipe(const char* exec, size_t* sz)
{
    DEBUG2("[  ]: executeCommandPipe >%s<", exec);

    int outfp;
    pid_t pid = popen2(exec, NULL, &outfp);

    char* data = NULL;

    void*  temp[RSIZE];
    size_t haveRead = 0;
     
    while (1) {
        
	DEBUG2("[  ]: executeCommandPipe -> read");
	ssize_t num = read(outfp, temp, RSIZE);
	DEBUG2("[  ]: executeCommandPipe <- read %d", num);
	
        if (num <= 0) {  // EOF or error
            break;
	} else if (num > 0) {
            data = realloc(data, haveRead+num+1);
            if (!data) {
                DEBUG2("[  ]: executeCommandPipe error on realloc %zd", haveRead+num);
                return NULL;
            }
            memcpy(data+haveRead, temp, num);
            haveRead += num;
            *(data + haveRead) = '\0';
        }

        //DEBUG2("[  ]: executeCommandPipe realloc %zd", haveRead);

        if (num < RSIZE) {
            break;
        }
   }
    close(outfp);

    DEBUG2("[  ]: executeCommandPipe got %zd bytes", haveRead);
    (*sz) = haveRead;
    return data;
} 
*/

#define	RDR	0
#define	WTR	1

static char bin_shell[] = "/bin/sh" ;
static char shell[] = "sh";
static char shflg[] = "-c";

//
// function non-reenterable
// (forked child will not close fd's opened by other popen_r's
// 
static int popen_r(const char* cmd, pid_t* pid)
{
    int     p[2];
    register int myside, yourside;

    if (pipe(p) < 0) {
    	return -1;
    }
    
    myside   = p[RDR];
    yourside = p[WTR];
    
    if( ((*pid) = fork()) == 0) {
    	
	// myside and yourside reverse roles in child
 
     	(void) close(myside);
    	(void) close(STDOUT_FILENO);
    	(void) fcntl(yourside, F_DUPFD, STDOUT_FILENO);
    	(void) close(yourside);
    	(void) execl(bin_shell, shell, shflg, cmd, (char *)0);
    	_exit(1);
    }
    if ((*pid) == -1) {
        return -1;
    }
    (void) close(yourside);
    
    return myside;
}

static int pclose_r(int fd, pid_t pid)
{
    register int r;
    int status;
    void (*hstat)(), (*istat)(), (*qstat)();

    (void) close(fd);
    istat = signal(SIGINT,  SIG_IGN);
    qstat = signal(SIGQUIT, SIG_IGN);
    hstat = signal(SIGHUP,  SIG_IGN);

    // while the child is not done and no error has occured wait in the loop
    while ((r = wait(&status)) != pid && (r != -1 || errno == EINTR)) {
        usleep(1000);
    }  
    if (r == -1) {
        status = -1;
    } 
      
    (void) signal(SIGINT,  istat);
    (void) signal(SIGQUIT, qstat);
    (void) signal(SIGHUP,  hstat);
    
    return status;
}

// seems this version works

char* executeCommandPipe(const char* exec, size_t* sz)
{
    DEBUG2("[  ]: executeCommandPipe >%s<", exec);
    
    pid_t pid;
    int fd = popen_r(exec, &pid);
    if (fd < 0) {
        logger(L_ERR, "[  ]: Error on popen()");
        return NULL;
    } 

    fcntl(fd, F_SETFL, O_NONBLOCK);

    char* data = NULL;

    void*  temp[RSIZE];
    size_t haveRead = 0;
    
    int maxWait = getWaitTime();
    time_t start = time(NULL);
    if (maxWait >= 0) {
        DEBUG2("[  ]: executeCommandPipe WaitTime %d", maxWait);
    }

    while (1) {
    
        size_t num = read(fd, temp, RSIZE);

        if (num == -1 && errno == EAGAIN)  {
 	        //DEBUG2("[  ]: executeCommandPipe EAGAIN");
	        usleep(1000);
        } else if (num > 0) {
	
	        //DEBUG2("[  ]: executeCommandPipe read %d", num);
            data = realloc(data, haveRead+num+1);
            if (!data) {
                DEBUG2("[  ]: executeCommandPipe error on realloc %zd", haveRead+num);
                return NULL;
            }
            memcpy(data+haveRead, temp, num);
            haveRead += num;
            *(data + haveRead) = '\0';
        } else {
	        break;
	    }

        //DEBUG2("[  ]: executeCommandPipe realloc %zd", haveRead);

	    if (maxWait > 0) {
	        time_t now = time(NULL); 
	        double elapsed = difftime(now, start);
	        if (elapsed > maxWait) {
	            WARNING2("[  ]: executeCommandPipe wait time exceeded");
		        kill(pid, SIGTERM);
		        break;
            }
	    }
    }

    pclose_r(fd, pid);

    DEBUG2("[  ]: executeCommandPipe got %zd bytes", haveRead);
    (*sz) = haveRead;
    
    return data;
}

/* this version is simple, but gets blocked if command runs forever

char* executeCommandPipe(const char* exec, size_t* sz)
{
    DEBUG2("[  ]: executeCommandPipe >%s<", exec);

    FILE *fp = popen(exec, "r");
    if (fp == NULL) {
        logger(L_ERR, "[EX]: Error on popen()");
        return NULL;
    }

    char* data = NULL;

    void*  temp[RSIZE];
    size_t haveRead = 0;

    while (1) {
        size_t num = fread(temp, 1, RSIZE, fp);

        if (num > 0) {
            data = realloc(data, haveRead+num+1);
            if (!data) {
                DEBUG2("[  ]: executeCommandPipe error on realloc %zd", haveRead+num);
                return NULL;
            }
            memcpy(data+haveRead, temp, num);
            haveRead += num;
            *(data + haveRead) = '\0';
        }

        //DEBUG2("[  ]: executeCommandPipe realloc %zd", haveRead);

        if (num < RSIZE) {
            break;
        }
    }


    //int status = 

    pclose(fp);

    //if (status == -1) {
    // Error reported by pclose()
    //...
    //} else {
    // Use macros described under wait() to inspect `status' in order
    //   to determine success/failure of command executed by popen()
    //...
    //}

    DEBUG2("[  ]: executeCommandPipe got %zd bytes", haveRead);
    (*sz) = haveRead;
    return data;
}*/


void peerName(int peer, char* buf, int sz)
{
    struct sockaddr_storage addr; 
    socklen_t len = sizeof(addr);

    if (getpeername(peer, (struct sockaddr*)&addr, &len) == 0) {

        // deal with both IPv4 and IPv6:
        if (addr.ss_family == AF_INET) {
    	    struct sockaddr_in *s = (struct sockaddr_in *)&addr;
    	    inet_ntop(AF_INET, &s->sin_addr, buf, sz);
        } else {	    // AF_INET6
    	    struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
    	    inet_ntop(AF_INET6, &s->sin6_addr, buf, sz);
        }
    }
}

static const char* dirTmp;

static int filterFiles(const struct dirent *dp)
{
    string_t *path = stringNew(dirTmp);
    stringAppend(path,"/");
    stringAppend(path,dp->d_name);
    
    struct stat buf;
    int stat = lstat(path->str, &buf);
    //printf("%d %s %s\n", stat,dp->d_name,strerror(errno));
    return (stat == 0 && !S_ISDIR(buf.st_mode));
}

static int filterDirs(const struct dirent *dp)
{
    string_t *path = stringNew(dirTmp);
    stringAppend(path,"/");
    stringAppend(path,dp->d_name);
    
    struct stat buf;
    int stat = lstat(path->str, &buf);
    //printf("%d %s %s\n", stat,dp->d_name,strerror(errno));
    return (stat == 0 && S_ISDIR(buf.st_mode));
}

// mimic "ls -F --quoting-style=shell" to little extent
string_t* executeDirListCommand(int type, const char* directory)
{
    DEBUG2("[EX]: executeDirListCommand get listing of %s", directory);
    struct stat buf;
    int stat = lstat(directory, &buf);
   
    if (stat < 0) {
        DEBUG2("[EX]: executeDirListCommand can not get %s", directory);
        return NULL;
    }
    
    if (S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode)) {
        
        string_t *file = stringNew("");

        if (type == ID_SET_ILIST) {
            stringAppend(file,"file:");
        }
        stringAppend(file,directory);

        return file;
	
    } else if (S_ISDIR(buf.st_mode)) {
    
        DEBUG2("[EX]: executeDirListCommand: directory");
        
        dirTmp = directory;  // small trick
    
        struct dirent **namelist = NULL;
	
        string_t *folders = stringNew("");
        
        int comma = 0;
	
        int n = scandir(directory, &namelist, filterDirs, alphasort);
        if (n >= 0) {
	
	        int i = 0;
 	        for( ; i<n; i++) {
        	    //printf("%s\n", namelist[i]->d_name);

		        if (strcmp(namelist[i]->d_name,".") != 0) {
		            if (comma == 1) {
			            stringAppend(folders,",");
		            } else {
		                comma = 1;
		            }

 		            if (type == ID_SET_ILIST) {
			            stringAppend(folders,"folder:");
		            }
		            stringAppend(folders,namelist[i]->d_name);
		            stringAppend(folders,"/");
		        }
        	    free(namelist[i]);
            }
            free(namelist);
        }
        //DEBUG2("[EX]: executeDirListCommand: %s", folders->str);
	
	    n = scandir(directory, &namelist, filterFiles, alphasort);
        if (n >= 0) {
	
	        int i = 0;
 	        for( ; i<n; i++) {
        	    //printf("%s\n", namelist[i]->d_name);

		        if (strcmp(namelist[i]->d_name,".") != 0) {
		            if (comma == 1) {
			            stringAppend(folders,",");
		            } else {
		                comma = 1;
		            }

 		            if (type == ID_SET_ILIST) {
			            stringAppend(folders,"file:");
		            }
		            stringAppend(folders,namelist[i]->d_name);
		        }
                free(namelist[i]);
            }
            free(namelist);
        }
        //DEBUG2("[EX]: executeDirListCommand: %s", folders->str);
	    return folders;

	    /* gets items unsorted
	    DIR *dirp = opendir(directory);

	    if (dirp) {

	        struct dirent *dp;

                int fComma = 0;
                int dComma = 0;
	        string_t *files   = stringNew("");
	        string_t *folders = stringNew("");

	        string_t *file    = stringNew("");

	        while ((dp=readdir(dirp))) {

		    if (strcmp(dp->d_name,".") == 0) continue;

		    //DEBUG2("[EX]: executeDirListCommand process %s", dp->d_name);

		    stringTruncate(file,0);
		    stringAppend(file,directory);
		    stringAppend(file,"/");
		    stringAppend(file,dp->d_name);

		    int stat = lstat(file->str, &buf);
		    if (stat < 0) continue;

		    if (S_ISDIR(buf.st_mode)) {

 		        if (dComma == 1) {
			    stringAppend(folders,",");
		        } else {
		            dComma = 1;
		        }

 		        if (type == ID_SET_ILIST) {
			    stringAppend(folders,"folder:");
		        }
		        stringAppend(folders,dp->d_name);
		        stringAppend(folders,"/");

	            } else //if (S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode))
		          {

		        if (fComma == 1) {
			    stringAppend(files,",");
		        } else {
		            fComma = 1;
		        }

		        if (type == ID_SET_ILIST) {
			    stringAppend(files,"file:");
		        }
		        stringAppend(files,dp->d_name);

		    }
                }

	        stringFree(file, BOOL_YES);
                closedir(dirp);

	        if (folders->len > 0) {
	            stringAppend(folders,",");
	        }
	        stringAppend(folders,files->str);  // dirs, then files
	        stringFree(files,   BOOL_YES);

	        //DEBUG2("[EX]: executeDirListCommand DONE");
	        return folders;
        }*/
    }   
    return NULL; 
}

//////////////////////////////////////////////////////////////////////////////////
//
// IP address detection
//
//////////////////////////////////////////////////////////////////////////////////

static int get_iface_list(struct ifconf *ifconf)
{
   int sock, rval;

   sock = socket(AF_INET,SOCK_STREAM,0);
   if(sock < 0)
   {
       perror("socket");
       return (-1);
   }

   if((rval = ioctl(sock, SIOCGIFCONF , (char*) ifconf  )) < 0 ) {
       perror("ioctl(SIOGIFCONF)");
   }

   close(sock);

   return rval;
}

string_t* getLocalIP()
{
    struct ifreq ifreqs[20];
    struct ifconf ifconf;

    memset(&ifconf,0,sizeof(ifconf));
    ifconf.ifc_buf = (char*) (ifreqs);
    ifconf.ifc_len = sizeof(ifreqs);

    if(get_iface_list(&ifconf) < 0) {
        return NULL;
    }
    int nifaces =  ifconf.ifc_len/sizeof(struct ifreq);

    DEBUG2("[WS]: getLocalIP found %d interfaces", nifaces);

    int  i;
    for(i = 0; i < nifaces; i++) {
        DEBUG2("[WS]: interface #%d is %s", i, ifreqs[i].ifr_name);
        static char* lo = "lo";
        if (strcmp(lo,ifreqs[i].ifr_name) == 0) {
            continue;  // skip loopback
        }
        static char* loopback = "127.0.0.1";
        if (strcmp(loopback, inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr)) == 0) {
            continue;  // skip loopback
        }
        DEBUG2("[WS]: use local IP %s", inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr));
        return stringNew(inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr));
    }
    return NULL;
}
