/* spool.c
 *
 * Copyright (c) 1992-2000 by Mike Gleason.
 * All rights reserved.
 * 
 */

#include "syshdrs.h"

#ifdef HAVE_LONG_FILE_NAMES

#include "spool.h"
#ifdef ncftp
#	include "trace.h"
#endif
#include "util.h"

int gSpoolSerial = 0;
int gUnprocessedJobs = 0;
int gJobs = 0;
int gHaveSpool = -1;

extern FTPLibraryInfo gLib;
extern char gOurDirectoryPath[], gOurInstallationPath[];
extern void CloseControlConnection(const FTPCIPtr);



void
TruncBatchLog(void)
{
	char f[256];
	struct stat st;
	time_t t;
	int fd;

	if (gOurDirectoryPath[0] != '\0') { 
		time(&t);
		t -= 86400;
		(void) OurDirectoryPath(f, sizeof(f), kSpoolLog);
		if ((stat(f, &st) == 0) && (st.st_mtime < t)) {
			/* Truncate old log file.
			 * Do not remove it, since a process
			 * could still conceivably be going.
			 */
			fd = open(f, O_WRONLY|O_TRUNC, 00600);
			if (fd >= 0)
				close(fd);
		}
	}
}	/* TruncBatchLog */



int
MkSpoolDir(char *sdir, size_t size)
{
	struct stat st;
	*sdir = '\0';

	/* Don't create in root directory. */
	if (gOurDirectoryPath[0] != '\0') { 
		(void) OurDirectoryPath(sdir, size, kSpoolDir);
		if ((stat(sdir, &st) < 0) && (MkDirs(sdir, 00700) < 0)) {
			perror(sdir);
			return (-1);
		} else {
			return (0);
		}
	}
	return (-1);
}	/* MkSpoolDir */




void
SpoolName(const char *const sdir, char *sp, size_t size, int flag, int serial, time_t when)
{
	char sname[64];
	char dstr[32];
	struct tm *ltp;

	if ((when == (time_t) 0) || (when == (time_t) -1))
		(void) time(&when);
	ltp = localtime(&when);
	if (ltp == NULL) {
		/* impossible */
		(void) Strncpy(dstr, "19700101-000000", size);
	} else {
		(void) strftime(dstr, sizeof(dstr), "%Y%m%d-%H%M%S", ltp);
	}
	(void) Strncpy(sp, sdir, size);
	(void) sprintf(sname, "/%c-%010u-%04x-%s",
		flag,
		(unsigned int) getpid(),
		(serial % (16 * 16 * 16 * 16)),
		dstr
	);
	(void) Strncat(sp, sname, size);
}	/* SpoolName */




int
HaveSpool(void)
{
#if defined(WIN32) || defined(_WINDOWS)
	char ncftpbatch[260];

	if (gHaveSpool < 0) {
		gHaveSpool = 0;
		if (gOurInstallationPath[0] != '\0') {
			OurInstallationPath(ncftpbatch, sizeof(ncftpbatch), "ncftpbatch.exe");
			gHaveSpool = (access(ncftpbatch, F_OK) == 0) ? 1 : 0;
		}
	}
#elif defined(BINDIR)
	char ncftpbatch[256];

	if (gHaveSpool < 0) {
		STRNCPY(ncftpbatch, BINDIR);
		STRNCAT(ncftpbatch, "/");
		STRNCAT(ncftpbatch, "ncftpbatch");
		gHaveSpool = (access(ncftpbatch, X_OK) == 0) ? 1 : 0;
	}
#else	/* BINDIR */
	if (gHaveSpool < 0) {
		if (geteuid() == 0) {
			gHaveSpool = (access("/usr/bin/ncftpbatch", X_OK) == 0) ? 1 : 0;
		} else {
			gHaveSpool = (system("ncftpbatch -X") == 0) ? 1 : 0;
		}
	}
#endif /* BINDIR */

	return (gHaveSpool);
}	/* HaveSpool */




int
CanSpool(void)
{
	char sdir[256];

	if (gOurDirectoryPath[0] == '\0') {
		return (-1);
	}
	if (MkSpoolDir(sdir, sizeof(sdir)) < 0)
		return (-1);
	return (0);
}	/* CanSpool */




int
SpoolX(
	const char *const op,
	const char *const rfile,
	const char *const rdir,
	const char *const lfile,
	const char *const ldir,
	const char *const host,
	const char *const ip,
	const unsigned int port,
	const char *const user,
	const char *const passclear,
	int xtype,
	int recursive,
	int delete,
	int passive,
	time_t when)
{
	char sdir[256];
	char pass[160];
	char spathname[256];
	char spathname2[256];
	char ldir2[256];
	FILE *fp;
#if defined(WIN32) || defined(_WINDOWS)
#else
	mode_t um;
#endif

	if (MkSpoolDir(sdir, sizeof(sdir)) < 0)
		return (-1);

	gSpoolSerial++;
	SpoolName(sdir, spathname2, sizeof(spathname2), op[0], gSpoolSerial, when);
	SpoolName(sdir, spathname, sizeof(spathname), 'z', gSpoolSerial, when);
#if defined(WIN32) || defined(_WINDOWS)
	fp = fopen(spathname, FOPEN_WRITE_TEXT);
#else
	um = umask(077);
	fp = fopen(spathname, FOPEN_WRITE_TEXT);
	(void) umask(um);
#endif
	if (fp == NULL)
		return (-1);

	if (fprintf(fp, "# This is a NcFTP spool file entry.\n# Run the \"ncftpbatch\" program to process the spool directory.\n#\n") < 0)
		goto err;
	if (fprintf(fp, "op=%s\n", op) < 0)
		goto err;
	if (fprintf(fp, "hostname=%s\n", host) < 0)
		goto err;
	if ((ip != NULL) && (ip[0] != '\0') && (fprintf(fp, "host-ip=%s\n", ip) < 0))
		goto err;
	if ((port > 0) && (port != (unsigned int) kDefaultFTPPort) && (fprintf(fp, "port=%u\n", port) < 0))
		goto err;
	if ((user != NULL) && (user[0] != '\0') && (strcmp(user, "anonymous") != 0) && (fprintf(fp, "user=%s\n", user) < 0))
		goto err;
	if ((strcmp(user, "anonymous") != 0) && (passclear != NULL) && (passclear[0] != '\0')) {
		(void) memcpy(pass, kPasswordMagic, kPasswordMagicLen);
		ToBase64(pass + kPasswordMagicLen, passclear, strlen(passclear), 1);
		if (fprintf(fp, "pass=%s\n", pass) < 0)
			goto err;
	} else if ((strcmp(user, "anonymous") == 0) && (gLib.defaultAnonPassword[0] != '\0')) {
		if (fprintf(fp, "anon-pass=%s\n", gLib.defaultAnonPassword) < 0)
			goto err;
	}
	if (fprintf(fp, "xtype=%c\n", xtype) < 0)
		goto err;
	if ((recursive != 0) && (fprintf(fp, "recursive=%s\n", YESNO(recursive)) < 0))
		goto err;
	if ((delete != 0) && (fprintf(fp, "delete=%s\n", YESNO(delete)) < 0))
		goto err;
	if (fprintf(fp, "passive=%d\n", passive) < 0)
		goto err;
	if (fprintf(fp, "remote-dir=%s\n", rdir) < 0)
		goto err;
	if ((ldir == NULL) || (ldir[0] == '\0') || (strcmp(ldir, ".") == 0)) {
		/* Use current process' working directory. */
		FTPGetLocalCWD(ldir2, sizeof(ldir2));
		if (fprintf(fp, "local-dir=%s\n", ldir2) < 0)
			goto err;
	} else {
		if (fprintf(fp, "local-dir=%s\n", ldir) < 0)
			goto err;
	}
	if (fprintf(fp, "remote-file=%s\n", rfile) < 0)
		goto err;
	if (fprintf(fp, "local-file=%s\n", lfile) < 0)
		goto err;

	if (fclose(fp) < 0)
		goto err2;

	/* Move the spool file into its "live" name. */
	if (rename(spathname, spathname2) < 0) {
		perror("rename spoolfile failed");
		goto err3;
	}
	gUnprocessedJobs++;
	return (0);

err:
	(void) fclose(fp);
err2:
	perror("write to spool file failed");
err3:
	(void) unlink(spathname);
	return (-1);
}



#if defined(WIN32) || defined(_WINDOWS)
#else
static int
PWrite(int sfd, const char *const buf0, size_t size)
{
	int nleft;
	const char *buf = buf0;
	int nwrote;

	nleft = (int) size;
	for (;;) {
		nwrote = write(sfd, buf, nleft);
		if (nwrote < 0) {
			if (errno != EINTR) {
				nwrote = size - nleft;
				if (nwrote == 0)
					nwrote = -1;
				return (nwrote);
			} else {
				errno = 0;
				nwrote = 0;
				/* Try again. */
			}
		}
		nleft -= nwrote;
		if (nleft <= 0)
			break;
		buf += nwrote;
	}
	nwrote = size - nleft;
	return (nwrote);
}	/* PWrite */
#endif



void
Jobs(void)
{
#if defined(WIN32) || defined(_WINDOWS)
	assert(0); // Not supported
#else
	char *argv[8];
	pid_t pid;
#ifdef BINDIR
	char ncftpbatch[256];

	STRNCPY(ncftpbatch, BINDIR);
	STRNCAT(ncftpbatch, "/");
	STRNCAT(ncftpbatch, "ncftpbatch");
#endif	/* BINDIR */

	pid = fork();
	if (pid < 0) {
		perror("fork");
	} else if (pid == 0) {
		argv[0] = (char *) "ncftpbatch";
		argv[1] = (char *) "-l";
		argv[2] = NULL;

#ifdef BINDIR
		(void) execv(ncftpbatch, argv);
		(void) fprintf(stderr, "Could not run %s.  Is it in installed as %s?\n", argv[0], ncftpbatch);
#else	/* BINDIR */
		(void) execvp(argv[0], argv);
		(void) fprintf(stderr, "Could not run %s.  Is it in your $PATH?\n", argv[0]);
#endif	/* BINDIR */
		perror(argv[0]);
		exit(1);
	} else {
#ifdef HAVE_WAITPID
		(void) waitpid(pid, NULL, 0);
#else
		(void) wait(NULL);
#endif
	}
#endif
}	/* Jobs */




void
RunBatch(int Xstruct, const FTPCIPtr cip)
{
#if defined(WIN32) || defined(_WINDOWS)
	char ncftpbatch[260];
	const char *prog;
	int winExecResult;

	if (gOurInstallationPath[0] == '\0') {
		(void) fprintf(stderr, "Cannot find path to %s.  Please re-run Setup.\n", "ncftpbatch.exe");
		return;
	}
	prog = ncftpbatch;
	OurInstallationPath(ncftpbatch, sizeof(ncftpbatch), "ncftpbatch.exe");
	
	winExecResult = WinExec(prog, SW_SHOWNORMAL);
	if (winExecResult <= 31) switch (winExecResult) {
		case ERROR_BAD_FORMAT:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The .EXE file is invalid");
			return;
		case ERROR_FILE_NOT_FOUND:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified file was not found.");
			return;
		case ERROR_PATH_NOT_FOUND:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified path was not found.");
			return;
		default:
			fprintf(stderr, "Could not run %s: Unknown error #%d.\n", prog, winExecResult);
			return;
	}
#else
	int pfd[2];
	char pfdstr[32];
	char *argv[8];
	pid_t pid = 0;
#ifdef BINDIR
	char ncftpbatch[256];

	STRNCPY(ncftpbatch, BINDIR);
	STRNCAT(ncftpbatch, "/");
	STRNCAT(ncftpbatch, "ncftpbatch");
#endif	/* BINDIR */

	if (Xstruct != 0) {
		if (pipe(pfd) < 0) {
			perror("pipe");
		}

		(void) sprintf(pfdstr, "%d", pfd[0]);
		pid = fork();
		if (pid < 0) {
			(void) close(pfd[0]);
			(void) close(pfd[1]);
			perror("fork");
		} else if (pid == 0) {
			(void) close(pfd[1]);	/* Child closes write end. */
			argv[0] = (char *) "ncftpbatch";
			argv[1] = (char *) "-d";
			argv[2] = (char *) "-|";
			argv[3] = pfdstr;
			argv[4] = NULL;

#ifdef BINDIR
			(void) execv(ncftpbatch, argv);
			(void) fprintf(stderr, "Could not run %s.  Is it in installed as %s?\n", argv[0], ncftpbatch);
#else	/* BINDIR */
			(void) execvp(argv[0], argv);
			(void) fprintf(stderr, "Could not run %s.  Is it in your $PATH?\n", argv[0]);
#endif	/* BINDIR */
			perror(argv[0]);
			exit(1);
		}
		(void) close(pfd[0]);	/* Parent closes read end. */
		(void) PWrite(pfd[1], (const char *) cip->lip, sizeof(FTPLibraryInfo));
		(void) PWrite(pfd[1], (const char *) cip, sizeof(FTPConnectionInfo));
		(void) close(pfd[1]);	/* Parent closes read end. */

		/* Close it now, or else this process would send
		 * the server a QUIT message.  This will cause it
		 * to think it already has.
		 */
		CloseControlConnection(cip);
	} else {
		pid = fork();
		if (pid < 0) {
			perror("fork");
		} else if (pid == 0) {
			argv[0] = (char *) "ncftpbatch";
			argv[1] = (char *) "-d";
			argv[2] = NULL;
			(void) execvp(argv[0], argv);
			(void) fprintf(stderr, "Could not run %s.  Is it in your $PATH?\n", argv[0]);
			perror(argv[0]);
			exit(1);
		}
	}

	if (pid > 1) {
#ifdef HAVE_WAITPID
		(void) waitpid(pid, NULL, 0);
#else
		(void) wait(NULL);
#endif
	}
#endif
}	/* RunBatch */



void
RunBatchIfNeeded(const FTPCIPtr cip)
{
	if (gUnprocessedJobs > 0) {
#ifdef ncftp
		Trace(0, "Running ncftp_batch for %d job%s.\n", gUnprocessedJobs, gUnprocessedJobs > 0 ? "s" : "");
#endif
		gUnprocessedJobs = 0;
		RunBatch(0, cip);
	}
}	/* RunBatchIfNeeded */

#endif	/* HAVE_LONG_FILE_NAMES */
