#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>

#include "extern.h"

#include "pcplisten.h"

#define PCPLISTEN "/bin/pcplisten"


int pcplisten_available(void)
{
	return 0 == access(PCPLISTEN, X_OK);
}

int pcplisten_start(/*INOUT*/struct sockaddr_storage *addr, int timeout_sec, /*OUT*/char **perrstr)
{
	FILE *fp;
	char buf[256];
	char ipstr[256];
	unsigned port;
	int status = 0;
	int result = -2;
#ifdef FTPD_DEBUG
	int errn = 0;
#endif /* FTPD_DEBUG */
	sighandler_t old_handler;

	*perrstr = 0;

	snprintf(buf, sizeof(buf), "%s tcp %s %u %u ftp-data", PCPLISTEN, sockaddr_addr2string(addr), sockaddr_port(addr), timeout_sec);
	buf[sizeof(buf)-1] = '\0';

	// SIGCHLD must not being set to SIG_IGN to make pclose() to work
	old_handler = signal(SIGCHLD, SIG_DFL);
	fp = popen(buf, "r");
	if (!fp) {
		signal(SIGCHLD, old_handler);
		return -1;
	}

	Log(("%s getting output", __FUNCTION__));
	if (buf == fgets(buf, sizeof(buf), fp)) {
		buf[sizeof(buf)-1] = '\0';
		Log(("%s got '%s'", __FUNCTION__, buf));
		if (2 == sscanf(buf, "OK: %255s %u", ipstr, &port) && port > 0 && port <= 65535) {
			int family =  AF_INET;
			if (strchr(ipstr, ':')) family = AF_INET6;
			struct sockaddr_storage *ss = numericaladdr2sockaddr_storage(family, ipstr);
			if (ss) {
				switch(ss->ss_family) {
					case AF_INET:  ((struct sockaddr_in *)ss)->sin_port = htons(port); break;
#ifdef USE_IPV6
					case AF_INET6: ((struct sockaddr_in6 *)ss)->sin6_port = htons(port); break;
#endif
				}
				memcpy(addr, ss, sizeof(*addr));
				free(ss);
				result = 0;
			}
		}
	} else buf[0] = '\0';

	Log(("%s pclose", __FUNCTION__));
	status = pclose(fp);
#ifdef FTPD_DEBUG
	if (-1 == status) errn = errno;
	Log(("%s pclose status=%d errn=%d", __FUNCTION__, status, errn));
#endif /* FTPD_DEBUG */
	signal(SIGCHLD, old_handler);
	if (-1 != status && WIFEXITED(status)) {
		status = WEXITSTATUS(status);
		Log(("%s pclose exit code=%d", __FUNCTION__, status));
		switch(status) {
		case 0: /* listen port opened */
			/* ok - dont change result */
			break;
		case 1: /* wrong usage */
			result = -1;
			break;
		case 2: /* pcp error */
			if (0 < strlen(buf))
			{ char *p = &buf[strlen(buf)];
			while(p > buf && (*(p-1) == '\n' || *(p-1) == '\r')) { p--; *p = '\0'; }
			if ('\0' != buf[0]) *perrstr = strdup(buf);
			}
			result = -1;
			break;
		case 3: /* no pcp server */
			// this is considered as ok, we want to use the original addr
			result = 0;
			break;
		default:
			result = -1;
		}
	}

	return result;
}