/* main.c */

#define _main_c_

#include "sys.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <arpa/ftp.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <pwd.h>

#ifdef SYSLOG
#	include <syslog.h>
#endif

#ifndef NO_UNISTDH
#	include <unistd.h>
#endif

#ifdef CURSES
#	undef HZ		/* Collides with HaZeltine ! */
#	include <curses.h>
#endif	/* CURSES */

#include "ftpdefs.h"
#include "defaults.h"
#include "cmds.h"
#include "main.h"
#include "ftp.h"
#include "ftprc.h"
#include "copyright.h"

/* main.c globals */
int					slrflag;
int					fromatty;			/* input is from a terminal */
char				*altarg;			/* argv[1] with no shell-like preprocessing  */
struct	servent		*sp;				/* service spec for tcp/ftp */
jmp_buf				toplevel;			/* non-local goto stuff for cmd scanner */
char				*line;				/* input line buffer */
char				*stringbase;		/* current scan point in line buffer */
char				*argbuf;			/* argument storage buffer */
char				*argbase;			/* current storage point in arg buffer */
int					margc;				/* count of arguments on input line */
char				*margv[20];			/* args parsed from input line */
struct userinfo		uinfo;				/* a copy of their pwent really */
int					ansi_escapes;		/* for fancy graphics */
int					ignore_rc;			/* are we supposed to ignore the netrc */
string				progname;			/* simple filename */
string				prompt, prompt2;	/* shell prompt string */
string				anon_password;		/* most likely your email address */
string				pager;				/* program to browse text files */
string				version = FTP_VERSION;
long				eventnumber;		/* number of commands we've done */
FILE				*logf = NULL;		/* log user activity */
string				logfname;			/* name of the logfile */
long				logsize = 4096L;	/* max log size. 0 == no limit */
int					percent_flags;		/* "%" in prompt string? */
int					at_flags;			/* "@" in prompt string? */
string 				mail_path;			/* your mailbox */
int					newmail;			/* how many new letters you have */
time_t				mbox_time;			/* last modified time of mbox */

char				*tcap_normal = "\033[0m";	/* Default ANSI escapes */
char				*tcap_boldface = "\033[1m";
char				*tcap_underline = "\033[4m";
char				*tcap_reverse = "\033[7m";

#ifdef CURSES
static char			tcbuf[2048];
#endif

/* main.c externs */
extern int			debug, verbose, mprompt;
extern int			options, cpend, data, connected;
extern int			curtype, macnum;
extern FILE			*cout;
extern struct cmd	cmdtab[];
extern str32		curtypename;
extern char			*macbuf;
extern char			*reply_string;
extern string		hostname, cwd, lcwd;
extern int			Optind;
extern char			*Optarg;

main(int argc, char **argv)
{
	register char		*cp;
	int					top, opt, openopts = 0;
	string				tmp, oline;	

	if ((cp = rindex(argv[0], '/'))) cp++;
	else cp = argv[0];
	(void) Strncpy(progname, cp);
	
	sp = getservbyname("ftp", "tcp");
	sp->s_port = 21;
	if (sp == 0) fatal("ftp/tcp: unknown service");

	if (init_arrays())			/* Reserve large blocks of memory now */
		fatal("could not reserve large amounts of memory.");

	/*
	 * Set up defaults for FTP.
	 */
	mprompt = dMPROMPT;
	verbose = dVERBOSE;
	debug = dDEBUG;

	(void) Strncpy(curtypename, dTYPESTR);
	curtype = dTYPE;
	(void) Strncpy(prompt, dPROMPT);
	
	/*	Setup our pager variable, before we run through the rc,
		which may change it. */
	set_pager(getenv("PAGER"), 0);
#ifdef CURSES
	ansi_escapes = 1;
#else
	ansi_escapes = 0;
	if ((cp = getenv("TERM")) != NULL) {
		if ((*cp == 'v' && cp[1] == 't')		/* vt100, vt102, ... */
			|| (strcmp(cp, "xterm") == 0))
			ansi_escapes = 1;
	}
#endif
	(void) getuserinfo();
	newmail = 0;
	(void) time(&mbox_time);
	(void) Strncpy(anon_password, uinfo.username);
	if (getlocalhostname(uinfo.hostname, sizeof(uinfo.hostname)) == 0) {
		(void) Strncat(anon_password, "@");
		(void) Strncat(anon_password, uinfo.hostname);
	}
#if dLOGGING
	(void) sprintf(logfname, "%s/%s", uinfo.homedir, dLOGNAME);
#else
	*logfname = 0;
#endif
	(void) get_cwd(lcwd, (int) sizeof(lcwd));

#ifdef SYSLOG
#	ifdef LOG_LOCAL3
	openlog ("NcFTP", LOG_PID, LOG_LOCAL3);
#	else
	openlog ("NcFTP", LOG_PID);
#	endif
#endif				/* SYSLOG */


	ignore_rc = 0;
	(void) strcpy(oline, "open ");
	while ((opt = Getopt(argc, argv, "DVINRHaiup:rd:g:")) >= 0) {
		switch(opt) {
			case 'a':
			case 'i':
			case 'u':
			case 'r':
				(void) sprintf(tmp, "-%c ", opt);
				goto cattmp;

			case 'p':
			case 'd':
			case 'g':
				(void) sprintf(tmp, "-%c %s ", opt, Optarg);
			cattmp:
				(void) strcat(oline, tmp);
				openopts++;
				break;

			case 'D':
				/* options |= SO_DEBUG; done below... */
				debug++;
				break;
			
			case 'V':
				verbose++;
				break;

			case 'I':
				mprompt = !mprompt;
				break;

			case 'N':
				ignore_rc = !ignore_rc;
				break;

			case 'H':
				show_version(0, NULL);
				exit (0);

			default:
			usage:
				(void) fprintf(stderr, "Usage: %s [program options] [[open options] site.to.open[:path]]\n\
Program Options:\n\
    -D     : Increase debug level.\n\
    -H     : Show version and compilation information.\n\
    -I     : Toggle interactive (mprompt) mode.\n\
    -N     : Toggle reading of the .netrc/.ncftprc.\n\
    -V     : Increase verbosity.\n\
Open Options:\n\
    -a     : Open anonymously (this is the default).\n\
    -u     : Open, specify user/password.\n\
    -i     : Ignore machine entry in your .netrc.\n\
    -p N   : Use port #N for connection.\n\
    -r     : \"Redial\" until connected.\n\
    -d N   : Redial, pausing N seconds between tries.\n\
    -g N   : Redial, giving up after N tries.\n\
    :path  : Open site, retrieve file \"path,\" then exit.\n\
Examples:\n\
    %s ftp.unl.edu:/pub/README\n\
    %s -V -u ftp.unl.edu\n\
    %s -D -r -d 120 -g 10 ftp.unl.edu\n", progname, progname, progname, progname);
			exit(1);
		}
	}

	cp = argv[Optind];  /* the site to open. */
	if (cp == NULL) {
		if (openopts)
			goto usage;
	} else
		(void) strcat(oline, cp);

	if (ignore_rc == 0)
		(void) thrash_rc();

	(void) fix_options();	/* adjust "options" according to "debug"  */
	
	fromatty = isatty(fileno(stdin));
	cpend = 0;  /* no pending replies */
	
	if (*logfname)
		logf = fopen (logfname, "a");

	eventnumber = 0L;
	/* The user specified a host on the command line.  Open it now... */
	if (argc > 1 && cp) {
		if (setjmp(toplevel))
			exit(0);
		(void) signal(SIGINT, intr);
		(void) signal(SIGPIPE, lostpeer);
		(void) strcpy(line, oline);
		makeargv();
		(void) setpeer(margc, margv);
	}

	(void) init_prompt();

	eventnumber = 1L;
	if (ansi_escapes) {
#ifndef CURSES
		(void) printf("%s%s Ready.%s\n", 
				tcap_boldface, FTP_VERSION, tcap_normal);
#else
		string vis;
		(void) sprintf(vis, "%s%s Ready.%s\n", 
				tcap_boldface, FTP_VERSION, tcap_normal);
		tcap_put(vis);
#endif /* !CURSES */
	}
	else
		(void) printf("%s Ready.\n", FTP_VERSION);
	top = setjmp(toplevel) == 0;
	if (top) {
		(void) signal(SIGINT, intr);
		(void) signal(SIGPIPE, lostpeer);
	}
	for (;;) {
		(void) cmdscanner(top);
		top = 1;
	}
}	/* main */



/*ARGSUSED*/
void intr(int unused)
{
	(void) longjmp(toplevel, 1);
}	/* intr */



int getuserinfo(void)
{
	register char			*cp;
	struct passwd			*pw = NULL;
	string					str;
	extern char				*home;	/* for glob.c */
	
	cp = getlogin();
	if (cp != NULL)
		pw = getpwnam(cp);
	if (pw == NULL)
		pw = getpwuid(getuid());
	if (pw != NULL) {
		(void) Strncpy(uinfo.username, pw->pw_name);
		(void) Strncpy(uinfo.shell, pw->pw_shell);
		(void) Strncpy(uinfo.homedir, pw->pw_dir);
		uinfo.uid = pw->pw_uid;
		home = uinfo.homedir;	/* for glob.c */
		if (((cp = getenv("MAIL")) == NULL) && ((cp = getenv("mail")) == NULL)) {
			(void) sprintf(str, "/usr/spool/mail/%s", uinfo.username);
			cp = str;
		}
		/*	mbox variable may be like MAIL=(28 /usr/mail/me /usr/mail/you),
			so try to find the first mail path.  */
		while (*cp != '/')
			cp++;
		(void) Strncpy(mail_path, cp);
		if ((cp = index(mail_path, ' ')) != NULL)
			*cp = '\0';
		return (0);
	} else {
		(void) Strncpy(uinfo.username, "unknown");
		(void) Strncpy(uinfo.shell, "/bin/sh");
		(void) Strncpy(uinfo.homedir, ".");	/* current directory */
		uinfo.uid = 999;
		return (-1);
	}
}	/* getuserinfo */




int init_arrays(void)
{
	if ((macbuf = (char *) malloc((size_t)(MACBUFLEN))) == NULL)
		goto barf;
	if ((line = (char *) malloc((size_t)(CMDLINELEN))) == NULL)
		goto barf;
	if ((argbuf = (char *) malloc((size_t)(CMDLINELEN))) == NULL)
		goto barf;
	if ((reply_string = (char *) malloc((size_t)(RECEIVEDLINELEN))) == NULL)
		goto barf;
	
	*macbuf = '\0';
	init_transfer_buffer();
	return (0);
barf:
	return (-1);
}	/* init_arrays */



#ifndef BUFSIZ
#define BUFSIZ 512
#endif

void init_transfer_buffer(void)
{
	extern char *xferbuf;
	extern size_t xferbufsize;
	
	/* Make sure we use a multiple of BUFSIZ for efficiency. */
	xferbufsize = (MAX_XFER_BUFSIZE / BUFSIZ) * BUFSIZ;
	while (1) {
		xferbuf = (char *) malloc (xferbufsize);
		if (xferbuf != NULL || xferbufsize < 1024)
			break;
		xferbufsize >>= 2;
	}
	
	if (xferbuf != NULL) return;
	fatal("out of memory for transfer buffer.");
}	/* init_transfer_buffer */




void init_prompt(void)
{
	register char *cp;
	
	percent_flags = at_flags = 0;
	for (cp = prompt; *cp; cp++) {
		if (*cp == '%') percent_flags = 1;
		else if (*cp == '@') at_flags = 1;
	}
}	/* init_prompt */



/*ARGSUSED*/
void lostpeer(int unused)
{
	if (connected) {
		close_streams(1);
		if (data >= 0) {
			(void) shutdown(data, 1+1);
			(void) close(data);
			data = -1;
		}
		connected = 0;
	}
	if (connected) {
		close_streams(1);
		connected = 0;
	}
	hostname[0] = cwd[0] = 0;
	macnum = 0;
}	/* lostpeer */



/*
 * Command parser.
 */
void cmdscanner(int top)
{
	register struct cmd *c;
#ifdef CURSES
	string vis, *vp;
#endif

	if (!top)
		(void) putchar('\n');
	for (;;) {
		if (fromatty) {
#ifndef CURSES
			(void) printf(strprompt());
#else
			(void) Strncpy(vis, (strprompt()));
			tcap_put(vis);
#endif /* !CURSES */
			(void) fflush(stdout);
		}
		if (Gets(line, (size_t)CMDLINELEN) == 0) {
			if (feof(stdin) || ferror(stdin))
				(void) quit(0, NULL);	/* control-d */
			break;
		}
		if (line[0] == 0)	/* blank line */
			break;
		eventnumber++;
		if (debug > 1)
			(void) printf("---> \"%s\"\n", line);
		(void) makeargv();
		if (margc == 0) {
			continue;	/* blank line... */
		}
		c = getcmd(margv[0]);
		if (c == (struct cmd *) -1) {
			(void) printf("?Ambiguous command\n");
			continue;
		}
		if (c == 0) {
			if (!implicit_cd(margv[0]))
				(void) printf("?Invalid command\n");
			continue;
		}
		if (c->c_conn && !connected) {
			(void) printf ("Not connected.\n");
			continue;
		}
		(*c->c_handler)(margc, margv);
		if (c->c_handler != help)
			break;
	}
	(void) signal(SIGINT, intr);
	(void) signal(SIGPIPE, lostpeer);
}	/* cmdscanner */




char *strprompt(void)
{
	time_t					tyme;
	char					eventstr[8];
	register char			*p, *q;
	string					str;
#ifdef CURSES
	static int 				virgin = 0;

	if (!virgin++ && ansi_escapes)
		termcap_init();
#endif /* CURSES */


	if (at_flags == 0 && percent_flags == 0)
		return (prompt);	/* But don't overwrite it! */

	if (at_flags) {
		for (p = prompt, q = prompt2, *q = 0; (*p); p++)
			if (*p == '@') switch (islower(*p) ? (toupper(*++p)) : (*++p)) {
				case '\0':
					--p;
					break;
				case 'M':
					if (CheckNewMail() > 0)
						q = Strpcpy(q, "(Mail) ");
					break;
				case 'N':
					q = Strpcpy(q, "\n");
					break;
				case 'P':	/* reset to no bold, no uline, no inverse, etc. */
					if (ansi_escapes)
						q = Strpcpy(q, tcap_normal);
					break;
				case 'B':	/* toggle boldface */
					if (ansi_escapes)
						q = Strpcpy(q, tcap_boldface);
					break;
				case 'U':	/* toggle underline */
					if (ansi_escapes)
						q = Strpcpy(q, tcap_underline);
					break;
				case 'R':
				case 'I':	/* toggle inverse (reverse) video */
					if (ansi_escapes)
						q = Strpcpy(q, tcap_reverse);
					break;
				case 'D':	/* insert current directory */
					if (cwd != NULL)
						q = Strpcpy(q, cwd);
					break;
				case 'H':	/* insert name of connected host */
					if (hostname != NULL)
						q = Strpcpy(q, hostname);
					break;
				case '!':
				case 'E':	/* insert event number */
					(void) sprintf(eventstr, "%ld", eventnumber);
					q = Strpcpy(q, eventstr);
					break;
				default:
					*q++ = *p;	/* just copy it; unknown switch */
			} else
				*q++ = *p;
		*q = '\0';
	} else 
		(void) strcpy(prompt2, prompt);
	
	if (percent_flags) {
		/*	only strftime if the user requested it (with a %something),
			otherwise don't waste time doing nothing. */
		(void) time(&tyme);
		(void) Strncpy(str, prompt2);
		(void) strftime(prompt2, sizeof(str), str, localtime(&tyme));
	}		
	return (prompt2);
}	/* strprompt */



char *Strpcpy(char *dst, char *src)
{
	while (*dst++ = *src++)
		;
	return (--dst);	/* return current value of dst, NOT original value! */
}	/* Strpcpy */




struct cmd *getcmd(char *name)
{
	register char *p, *q;
	register struct cmd *c, *found;
	register int nmatches, longest;

	if (name == NULL)
		return (NULL);
	longest = 0;
	nmatches = 0;
	found = 0;
	for (c = cmdtab; p = c->c_name; c++) {
		for (q = name; *q == *p++; q++)
			if (*q == 0)		/* exact match? */
				return (c);
		if (!*q) {			/* the name was a prefix */
			if (q - name > longest) {
				longest = q - name;
				nmatches = 1;
				found = c;
			} else if (q - name == longest)
				nmatches++;
		}
	}
	if (nmatches > 1)
		return ((struct cmd *)-1);
	return (found);
}	/* getcmd */




/*
 * Slice a string up into argc/argv.
 */

void makeargv(void)
{
	char **argp;

	margc = 0;
	argp = margv;
	stringbase = line;		/* scan from first of buffer */
	argbase = argbuf;		/* store from first of buffer */
	slrflag = 0;
	while (*argp++ = slurpstring())
		margc++;
}	/* makeargv */




/*
 * Parse string into argbuf;
 * implemented with FSM to
 * handle quoting and strings
 */
char *slurpstring(void)
{
	int got_one = 0;
	register char *sb = stringbase;
	register char *ap = argbase;
	char *tmp = argbase;		/* will return this if token found */

	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
		switch (slrflag) {	/* and $ as token for macro invoke */
			case 0:
				slrflag++;
				stringbase++;
				return ((*sb == '!') ? "!" : "$");
				/* NOTREACHED */
			case 1:
				slrflag++;
				altarg = stringbase;
				break;
			default:
				break;
		}
	}

S0:
	switch (*sb) {

	case '\0':
		goto OUT;

	case ' ':
	case '\t':
	case '\n':
	case '=':
		sb++; goto S0;

	default:
		switch (slrflag) {
			case 0:
				slrflag++;
				break;
			case 1:
				slrflag++;
				altarg = sb;
				break;
			default:
				break;
		}
		goto S1;
	}

S1:
	switch (*sb) {

	case ' ':
	case '\t':
	case '\n':
	case '=':
	case '\0':
		goto OUT;	/* end of token */

	case '\\':
		sb++; goto S2;	/* slurp next character */

	case '"':
		sb++; goto S3;	/* slurp quoted string */

	default:
		*ap++ = *sb++;	/* add character to token */
		got_one = 1;
		goto S1;
	}

S2:
	switch (*sb) {

	case '\0':
		goto OUT;

	default:
		*ap++ = *sb++;
		got_one = 1;
		goto S1;
	}

S3:
	switch (*sb) {

	case '\0':
		goto OUT;

	case '"':
		sb++; goto S1;

	default:
		*ap++ = *sb++;
		got_one = 1;
		goto S3;
	}

OUT:
	if (got_one)
		*ap++ = '\0';
	argbase = ap;			/* update storage pointer */
	stringbase = sb;		/* update scan pointer */
	if (got_one) {
		return(tmp);
	}
	switch (slrflag) {
		case 0:
			slrflag++;
			break;
		case 1:
			slrflag++;
			altarg = (char *) 0;
			break;
		default:
			break;
	}
	return((char *)0);
}	/* slurpstring */




#define HELPINDENT (sizeof ("directory"))

/*
 * Help command.
 * Call each command handler with argc == 0 and argv[0] == name.
 */
help(int argc, char **argv)
{
	register struct cmd		*c;
	int						i, showall = 0;
	char					*arg;

	if (argc == 2)
		showall = strcmp(argv[1], "all") == 0;
	if (argc == 1 || showall)  {
		(void) printf("Commands may be abbreviated.  'help all' shows aliases,\ninvisible and unsupported commands.  'help <command>' \ngives a brief description of <command>.  Commands are:\n");
		for (c = cmdtab, i=0; c->c_name != NULL; c++) {
			if (c->c_hidden && !showall) continue;
			(void) printf("%-13s", c->c_name);
			if (++i == 6) {
				i = 0;
				putchar('\n');
			}
		}
		if (i < 6)
			putchar('\n');
	} else while (--argc > 0) {
		arg = *++argv;
		c = getcmd(arg);
		if (c == (struct cmd *)-1)
			(void) printf("?Ambiguous help command %s\n", arg);
		else if (c == (struct cmd *)0)
			(void) printf("?Invalid help command %s\n", arg);
		else
			(void) printf("%-*s\t%s\n", HELPINDENT,
				c->c_name, c->c_help);
	}
}	/* help */


/*
 * If the user wants to, s/he can specify the maximum size of the log
 * file, so it doesn't waste too much disk space.  If the log is too
 * fat, trim the older lines (at the top) until we're under the limit.
 */
void trim_log(void)
{
	FILE				*new, *old;
	struct stat			st;
	long				fat;
	string				tmplogname, str;

	if (logsize <= 0 || *logfname == 0 || stat(logfname, &st) ||
		(old = fopen(logfname, "r")) == NULL)
		return;	/* never trim, or no log */
	fat = st.st_size - logsize;
	if (fat <= 0L) return;	/* log too small yet */
	while (fat > 0L) {
		if (FGets(str, old) == NULL) return;
		fat -= (long) strlen(str);
	}
	/* skip lines until a new site was opened */
	while (1) {
		if (FGets(str, old) == NULL) {
			(void) fclose(old);
			(void) unlink(logfname);
			return;	/* nothing left, start anew */
		}
		if (*str != '\t') break;
	}
	
	/* copy the remaining lines in "old" to "new" */
	(void) Strncpy(tmplogname, logfname);
	tmplogname[strlen(tmplogname) - 1] = 'T';
	if ((new = fopen(tmplogname, "w")) == NULL) {
		(void) Perror(tmplogname);
		return;
	}
	(void) fputs(str, new);
	while (FGets(str, old))
		(void) fputs(str, new);
	(void) fclose(old); (void) fclose(new);
	if (unlink(logfname) < 0)
		Perror(logfname);
	if (rename(tmplogname, logfname) < 0)
		Perror(tmplogname);
}	/* trim_log */




int CheckNewMail(void)
{
	struct stat stbuf;

	if (*mail_path == '\0') return 0;
	if (stat(mail_path, &stbuf) < 0) {	/* cant find mail_path so we'll */
		*mail_path = '\0';				/* never check it again */
		return 0;
	}

	if (stbuf.st_mtime > mbox_time) {
		newmail++;
		(void) printf("%s\n", NEWMAILMESSAGE);
		(void) time(&mbox_time);				/* only notify once. */
	}
	
	return newmail;
}	/* CheckNewMail */


#ifdef CURSES
void termcap_init(void)
{
	static char area[1024];
	static char *s = area;
	char *tgetstr(char *, char **);
	char *term;

	if (tgetent(tcbuf,(term = getenv("TERM"))) != 1) {
		(void) fprintf(stderr,"Can't get termcap entry for terminal [%s]\n", term);
	} else {
		if (!(tcap_normal = tgetstr("se", &s)))
			tcap_normal = "";
		if (!(tcap_boldface = tgetstr("md", &s)))
			tcap_boldface = "";
		if (!(tcap_underline = tgetstr("us", &s)))
			tcap_underline = "";
		if (!(tcap_reverse = tgetstr("so", &s)))
			tcap_reverse = "";
	}
}	/* termcap_init */



static int c_output(int c)
{
	putchar(c);
}	/* c_output */




void tcap_put(char *cap)
{
	tputs(cap, 0, c_output);
}	/* tcap_put */

#endif /* CURSES */

/* eof main.c */
