/* util.c
 *
 * Copyright (c) 1992-1999 by Mike Gleason.
 * All rights reserved.
 * 
 */

// This is a subset of NcFTP's util.c.

#include "syshdrs.h"
#include "util.h"

uid_t gUid;
char gUser[32];
char gHome[256];
char gShell[256];
char gOurDirectoryPath[260];
char gOurInstallationPath[260];

static const unsigned char B64EncodeTable[64] =
{
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
	'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
	'w', 'x', 'y', 'z', '0', '1', '2', '3',
	'4', '5', '6', '7', '8', '9', '+', '/'
};

static const unsigned char B64DecodeTable[256] =
{
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 000-007 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 010-017 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 020-027 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 030-037 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 040-047 */
	'\177', '\177', '\177', '\76', '\177', '\177', '\177', '\77',	/* 050-057 */
	'\64', '\65', '\66', '\67', '\70', '\71', '\72', '\73',		/* 060-067 */
	'\74', '\75', '\177', '\177', '\177', '\100', '\177', '\177',	/* 070-077 */
	'\177', '\0', '\1', '\2', '\3', '\4', '\5', '\6',	/* 100-107 */
	'\7', '\10', '\11', '\12', '\13', '\14', '\15', '\16',	/* 110-117 */
	'\17', '\20', '\21', '\22', '\23', '\24', '\25', '\26',		/* 120-127 */
	'\27', '\30', '\31', '\177', '\177', '\177', '\177', '\177',	/* 130-137 */
	'\177', '\32', '\33', '\34', '\35', '\36', '\37', '\40',	/* 140-147 */
	'\41', '\42', '\43', '\44', '\45', '\46', '\47', '\50',		/* 150-157 */
	'\51', '\52', '\53', '\54', '\55', '\56', '\57', '\60',		/* 160-167 */
	'\61', '\62', '\63', '\177', '\177', '\177', '\177', '\177',	/* 170-177 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 200-207 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 210-217 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 220-227 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 230-237 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 240-247 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 250-257 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 260-267 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 270-277 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 300-307 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 310-317 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 320-327 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 330-337 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 340-347 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 350-357 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 360-367 */
	'\177', '\177', '\177', '\177', '\177', '\177', '\177', '\177',		/* 370-377 */
};

void
ToBase64(void *dst0, const void *src0, size_t n, int terminate)
{
	unsigned char *dst;
	const unsigned char *src, *srclim;
	unsigned int c0, c1, c2;
	unsigned int ch;

	src = src0;
	srclim = src + n;
	dst = dst0;

	while (src < srclim) {
		c0 = *src++;
		if (src < srclim) {
			c1 = *src++;
		} else {
			c1 = 0;
		}
		if (src < srclim) {
			c2 = *src++;
		} else {
			c2 = 0;
		}

		ch = c0 >> 2;
		dst[0] = B64EncodeTable[ch & 077];

		ch = ((c0 << 4) & 060) | ((c1 >> 4) & 017);
		dst[1] = B64EncodeTable[ch & 077];

		ch = ((c1 << 2) & 074) | ((c2 >> 6) & 03);
		dst[2] = B64EncodeTable[ch & 077];

		ch = (c2 & 077);
		dst[3] = B64EncodeTable[ch & 077];

		dst += 4;
	}
	if (terminate != 0)
		*dst = '\0';
}						       /* ToBase64 */



void
FromBase64(void *dst0, const void *src0, size_t n, int terminate)
{
	unsigned char *dst;
	const unsigned char *src, *srclim;
	unsigned int c0, c1, c2, c3;
	unsigned int ch;

	src = src0;
	srclim = src + n;
	dst = dst0;

	while (src < srclim) {
		c0 = *src++;
		if (src < srclim) {
			c1 = *src++;
		} else {
			c1 = 0;
		}
		if (src < srclim) {
			c2 = *src++;
		} else {
			c2 = 0;
		}
		if (src < srclim) {
			c3 = *src++;
		} else {
			c3 = 0;
		}

		ch = (((unsigned int) B64DecodeTable[c0]) << 2) | (((unsigned int) B64DecodeTable[c1]) >> 4);
		dst[0] = (unsigned char) ch;

		ch = (((unsigned int) B64DecodeTable[c1]) << 4) | (((unsigned int) B64DecodeTable[c2]) >> 2);
		dst[1] = (unsigned char) ch;

		ch = (((unsigned int) B64DecodeTable[c2]) << 6) | (((unsigned int) B64DecodeTable[c3]));
		dst[2] = (unsigned char) ch;

		dst += 3;
	}
	if (terminate != 0)
		*dst = '\0';
}						       /* FromBase64 */



/* This will abbreviate a string so that it fits into max characters.
 * It will use ellipses as appropriate.  Make sure the string has
 * at least max + 1 characters allocated for it.
 */
void
AbbrevStr(char *dst, const char *src, size_t max, int mode)
{
	int len;

	len = (int) strlen(src);
	if (len > (int) max) {
		if (mode == 0) {
			/* ...Put ellipses at left */
			(void) strcpy(dst, "...");
			(void) Strncat(dst, (char *) src + len - (int) max + 3, max + 1);
		} else {
			/* Put ellipses at right... */
			(void) Strncpy(dst, (char *) src, max + 1);
			(void) strcpy(dst + max - 3, "...");
		}
	} else {
		(void) Strncpy(dst, (char *) src, max + 1);
	}
}	/* AbbrevStr */




char *
Path(char *const dst, const size_t siz, const char *const parent, const char *const fname)
{
	(void) Strncpy(dst, parent, siz);
	(void) Strncat(dst, LOCAL_PATH_DELIM_STR, siz);
	return (Strncat(dst, fname, siz));
}	/* Path */




char *
OurDirectoryPath(char *const dst, const size_t siz, const char *const fname)
{
	return (Path(dst, siz, gOurDirectoryPath, fname));
}	/* OurDirectoryPath */



char *
OurInstallationPath(char *const dst, const size_t siz, const char *const fname)
{
	return (Path(dst, siz, gOurInstallationPath, fname));
}	/* OurInstallationPath */




/* Create, if necessary, a directory in the user's home directory to
 * put our incredibly important stuff in.
 */
void
InitOurDirectory(void)
{
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
	DWORD dwType, dwSize;
	HKEY hkey;
	char *cp;
	int rc;

	ZeroMemory(gOurDirectoryPath, (DWORD) sizeof(gOurDirectoryPath));
	ZeroMemory(gOurInstallationPath, (DWORD) sizeof(gOurInstallationPath));

	if (RegOpenKeyEx(
		HKEY_LOCAL_MACHINE,
		"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\ncftp.exe",
		(DWORD) 0,
		KEY_QUERY_VALUE,
		&hkey) == ERROR_SUCCESS)
	{
		dwSize = (DWORD) (sizeof(gOurInstallationPath) - 1);
		dwType = 0;
		if (RegQueryValueEx(
			hkey, 
			NULL, 
			(DWORD *) 0, 
			&dwType, 
			(LPBYTE) gOurInstallationPath, 
			&dwSize) == ERROR_SUCCESS)
		{
			// This gave us the path to ncftp.exe;
			// But we use a subdirectory in that directory.
			//
			cp = StrRFindLocalPathDelim(gOurInstallationPath);
			if (cp == NULL)
				ZeroMemory(gOurInstallationPath, (DWORD) sizeof(gOurInstallationPath));
			else
				ZeroMemory(cp, (DWORD) (cp - gOurInstallationPath));
		}
		RegCloseKey(hkey);
	}
	
	if (gOurInstallationPath[0] == '\0') {
		if (GetModuleFileName(NULL, gOurInstallationPath, (DWORD) sizeof(gOurInstallationPath) - 1) <= 0) {
			ZeroMemory(gOurInstallationPath, (DWORD) sizeof(gOurInstallationPath));
		} else {
			// This gave us the path to the current .exe;
			// But we use a subdirectory in that directory.
			//
			cp = StrRFindLocalPathDelim(gOurInstallationPath);
			if (cp == NULL)
				ZeroMemory(gOurInstallationPath, (DWORD) sizeof(gOurInstallationPath));
			else
				ZeroMemory(cp, (DWORD) (cp - gOurInstallationPath));
		}
	}

	if (gOurInstallationPath[0] != '\0') {
		if ((cp = getenv("NCFTPDIR")) != NULL) {
			if (*cp == '"')
				cp++;
			(void) STRNCPY(gOurDirectoryPath, cp);
			cp = strrchr(gOurDirectoryPath, '"');
			if ((cp != NULL) && (cp[1] == '\0'))
				*cp = '\0';
		} else if ((cp = getenv("USERPROFILE")) != NULL) {
			if (*cp == '"')
				cp++;
			(void) STRNCPY(gOurDirectoryPath, cp);
			cp = strrchr(gOurDirectoryPath, '"');
			if ((cp != NULL) && (cp[1] == '\0'))
				*cp = '\0';
			(void) STRNCAT(gOurDirectoryPath, "\\");
			(void) STRNCAT(gOurDirectoryPath, kOurDirectoryName);
		} else {
			STRNCPY(gOurDirectoryPath, gOurInstallationPath);
			if (gUser[0] == '\0') {
				STRNCAT(gOurDirectoryPath, "\\Users\\default");
			} else {
				STRNCAT(gOurDirectoryPath, "\\Users\\");
				STRNCAT(gOurDirectoryPath, gUser);
			}
		}
		rc = MkDirs(gOurDirectoryPath, 00755);
	}

#else
	struct stat st;
	char *cp;

#ifdef BINDIR
	(void) STRNCPY(gOurInstallationPath, BINDIR);
#else
	memset(gOurInstallationPath, 0, sizeof(gOurInstallationPath));
#endif

	cp = getenv("NCFTPDIR");
	if (cp != NULL) {
		(void) STRNCPY(gOurDirectoryPath, cp);
	} else if (STREQ(gHome, "/")) {
		/* Don't create it if you're root and your home directory
		 * is the root directory.
		 *
		 * If you are root and you want to store your ncftp
		 * config files, move your home directory somewhere else,
		 * such as /root or /home/root.
		 */
		gOurDirectoryPath[0] = '\0';
		return;
	} else {
		(void) Path(gOurDirectoryPath,
			sizeof(gOurDirectoryPath),
			gHome,
			kOurDirectoryName
		);
	}

	if (stat(gOurDirectoryPath, &st) < 0) {
		if (mkdir(gOurDirectoryPath, 00755) < 0) {
			gOurDirectoryPath[0] = '\0';
		}
	}
#endif
}	/* InitOurDirectory */



void
InitUserInfo(void)
{
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
	DWORD nSize;
	char *cp;

	memset(gUser, 0, sizeof(gUser));
	nSize = sizeof(gUser) - 1;
	if (! GetUserName(gUser, &nSize))
		STRNCPY(gUser, "default");

	memset(gHome, 0, sizeof(gHome));
	(void) GetTempPath((DWORD) sizeof(gHome) - 1, gHome);
	cp = strrchr(gHome, '\\');
	if ((cp != NULL) && (cp[1] == '\0'))
		*cp = '\0';

	memset(gShell, 0, sizeof(gShell));
#else
	struct passwd *pwptr;
	char *envp;

	gUid = geteuid();
	pwptr = getpwuid(gUid);

	if (pwptr == NULL) {
		envp = getenv("LOGNAME");
		if (envp == NULL) {
			(void) fprintf(stderr, "Who are you?\n");
			(void) fprintf(stderr, "You have a user id number of %d, but no username associated with it.\n", (int) gUid);
			(void) STRNCPY(gUser, "unknown");
		} else {
			(void) STRNCPY(gUser, envp);
		}

		envp = getenv("HOME");
		if (envp == NULL)
			(void) STRNCPY(gHome, "/");
		(void) STRNCPY(gHome, envp);

		envp = getenv("SHELL");
		if (envp == NULL)
			(void) STRNCPY(gShell, "/bin/sh");
		(void) STRNCPY(gShell, envp);
	} else {
		/* Copy home directory. */
		(void) STRNCPY(gHome, pwptr->pw_dir);

		/* Copy user name. */
		(void) STRNCPY(gUser, pwptr->pw_name);

		/* Copy shell. */
		(void) STRNCPY(gShell, pwptr->pw_shell);
	}
#endif

	InitOurDirectory();
}	/* InitUserInfo */



int 
StrToBool(const char *const s)
{
	int c;
	int result;
	
	c = *s;
	if (isupper(c))
		c = tolower(c);
	result = 0;
	switch (c) {
		case 'f':			       /* false */
		case 'n':			       /* no */
			break;
		case 'o':			       /* test for "off" and "on" */
			c = (int) s[1];
			if (isupper(c))
				c = tolower(c);
			if (c == 'f')
				break;
			/* fall through */
		case 't':			       /* true */
		case 'y':			       /* yes */
			result = 1;
			break;
		default:			       /* 1, 0, -1, other number? */
			if (atoi(s) != 0)
				result = 1;
	}
	return result;
}						       /* StrToBool */



/* Read a line, and axe the end-of-line. */
char *
FGets(char *str, size_t size, FILE *fp)
{
	char *cp, *nlptr;
	
	cp = fgets(str, ((int) size) - 1, fp);
	if (cp != NULL) {
		cp[((int) size) - 1] = '\0';	/* ensure terminator */
		nlptr = cp + strlen(cp) - 1;
		if (*nlptr == '\n')
			*nlptr = '\0';
	} else {
		memset(str, 0, size);
	}
	return cp;
}	/* FGets */




char *
StrFindLocalPathDelim(const char *src) /* TODO: optimize */
{
	const char *first;
	int c;

	first = NULL;
	for (;;) {
		c = *src++;
		if (c == '\0')
			break;
		if (IsLocalPathDelim(c)) {
			first = src - 1;
			break;
		}
	}

	return ((char *) first);
}	/* StrFindLocalPathDelim */



char *
StrRFindLocalPathDelim(const char *src)	/* TODO: optimize */
{
	const char *last;
	int c;

	last = NULL;
	for (;;) {
		c = *src++;
		if (c == '\0')
			break;
		if (IsLocalPathDelim(c))
			last = src - 1;
	}

	return ((char *) last);
}	/* StrRFindLocalPathDelim */




int
MkDirs(const char *const newdir, int mode1)
{
	char s[512];
	int rc;
	char *cp, *sl;
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
	struct _stat st;
	char *share;
#elif defined(__CYGWIN__)
	char *share;
	struct Stat st;
	mode_t mode = (mode_t) mode1;
#else
	struct Stat st;
	mode_t mode = (mode_t) mode1;
#endif

	errno = 0;	/* We can return 0 but set errno to EEXIST */

#if defined(WIN32) || defined(_WINDOWS) || defined(__CYGWIN__)
	if ((isalpha(newdir[0])) && (newdir[1] == ':')) {
		if (! IsLocalPathDelim(newdir[2])) {
			/* Special case "c:blah", and errout.
			 * "c:\blah" must be used or _access GPFs.
			 */
			errno = EINVAL;
			return (-1);
		} else if (newdir[3] == '\0') {
			/* Special case root directory, which cannot be made. */
			errno = EEXIST;
			return (0);
		}
	} else if (IsUNCPrefixed(newdir)) {
		share = StrFindLocalPathDelim(newdir + 2);
		if ((share == NULL) || (StrFindLocalPathDelim(share + 1) == NULL)) {
			errno = ENOTDIR;
			return (-1);
		}
	}
#endif

#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
	if (_access(newdir, 00) == 0) {
		if (_stat(newdir, &st) < 0)
			return (-1);
		if (! S_ISDIR(st.st_mode)) {
			errno = ENOTDIR;
			return (-1);
		}
		errno = EEXIST;
		return 0;
	}
#else
	if (access(newdir, F_OK) == 0) {
		if (Stat(newdir, &st) < 0)
			return (-1);
		if (! S_ISDIR(st.st_mode)) {
			errno = ENOTDIR;
			return (-1);
		}
		errno = EEXIST;
		return 0;
	}
#endif

	(void) strncpy(s, newdir, sizeof(s));
	if (s[sizeof(s) - 1] != '\0') {
#ifdef ENAMETOOLONG
		errno = ENAMETOOLONG;
#else
		errno = EINVAL;
		return (-1);
#endif
	}

	cp = StrRFindLocalPathDelim(s);
	if (cp == NULL) {
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
		if (! CreateDirectory(newdir, (LPSECURITY_ATTRIBUTES) 0))
			return (-1);
		return (0);
#else
		rc = mkdir(newdir, mode);
		return (rc);
#endif
	} else if (cp[1] == '\0') {
		/* Remove trailing slashes from path. */
		--cp;
		while (cp > s) {
			if (! IsLocalPathDelim(*cp))
				break;
			--cp;
		}
		cp[1] = '\0';
		cp = StrRFindLocalPathDelim(s);
		if (cp == NULL) {
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
			if (! CreateDirectory(s, (LPSECURITY_ATTRIBUTES) 0))
				return (-1);
#else
			rc = mkdir(s, mode);
			return (rc);
#endif
		}
	}

	/* Find the deepest directory in this
	 * path that already exists.  When
	 * we do, we want to have the 's'
	 * string as it was originally, but
	 * with 'cp' pointing to the first
	 * slash in the path that starts the
	 * part that does not exist.
	 */
	sl = NULL;
	for (;;) {
		*cp = '\0';
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
		rc = _access(s, 00);
#else
		rc = access(s, F_OK);
#endif
		if (sl != NULL)
			*sl = LOCAL_PATH_DELIM;
		if (rc == 0) {
			*cp = LOCAL_PATH_DELIM;
			break;
		}
		sl = cp;
		cp = StrRFindLocalPathDelim(s);
		if (cp == NULL) {
			/* We do not have any more
			 * slashes, so none of the
			 * new directory's components
			 * existed before, so we will
			 * have to make everything
			 * starting at the first node.
			 */
			if (sl != NULL)
				*sl = LOCAL_PATH_DELIM;

			/* We refer to cp + 1 below,
			 * so this is why we can
			 * set "cp" to point to the
			 * byte before the array starts.
			 */
			cp = s;
			--cp;
			break;
		}
	}

	for (;;) {
		/* Extend the path we have to
		 * include the next component
		 * to make.
		 */
		sl = StrFindLocalPathDelim(cp + 1);
		if (sl == s) {
			/* If the next slash is pointing
			 * to the start of the string, then
			 * the path is an absolute path and
			 * we don't need to make the root node,
			 * and besides the next mkdir would
			 * try an empty string.
			 */
			++cp;
			sl = StrFindLocalPathDelim(cp + 1);
		}
		if (sl != NULL) {
			*sl = '\0';
		}
#if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__)
		if (! CreateDirectory(s, (LPSECURITY_ATTRIBUTES) 0))
			return (-1);
#else
		rc = mkdir(s, mode);
		if (rc < 0)
			return rc;
#endif
		if (sl == NULL)
			break;
		*sl = LOCAL_PATH_DELIM;
		cp = sl;
	}
	return (0);
}	/* MkDirs */
