cmd.c
changeset 0 1d0ce1ebbc72
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd.c	Tue Dec 06 13:51:55 2022 +0000
@@ -0,0 +1,637 @@
+/*
+ * Copyright (c) 2018 Sunil Nimmagadda <sunil@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/telnet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <histedit.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ftp.h"
+
+#define ARGVMAX	64
+
+static void	 cmd_interrupt(int);
+static int	 cmd_lookup(const char *);
+static FILE	*data_fopen(const char *);
+static void	 do_open(int, char **);
+static void	 do_help(int, char **);
+static void	 do_quit(int, char **);
+static void	 do_ls(int, char **);
+static void	 do_pwd(int, char **);
+static void	 do_cd(int, char **);
+static void	 do_get(int, char **);
+static void	 do_passive(int, char **);
+static void	 do_lcd(int, char **);
+static void	 do_lpwd(int, char **);
+static void	 do_put(int, char **);
+static void	 do_mget(int, char **);
+static void	 ftp_abort(void);
+static char	*prompt(void);
+
+static FILE	*ctrl_fp, *data_fp;
+
+static struct {
+	const char	 *name;
+	const char	 *info;
+	void		(*cmd)(int, char **);
+	int		  conn_required;
+} cmd_tbl[] = {
+	{ "open", "connect to remote ftp server", do_open, 0 },
+	{ "close", "terminate ftp session", do_quit, 1 },
+	{ "help", "print local help information", do_help, 0 },
+	{ "?", "print local help information", do_help, 0 },
+	{ "quit", "terminate ftp session and exit", do_quit, 0 },
+	{ "exit", "terminate ftp session and exit", do_quit, 0 },
+	{ "ls", "list contents of remote directory", do_ls, 1 },
+	{ "pwd", "print working directory on remote machine", do_pwd, 1 },
+	{ "cd", "change remote working directory", do_cd, 1 },
+	{ "nlist", "nlist contents of remote directory", do_ls, 1 },
+	{ "get", "receive file", do_get, 1 },
+	{ "passive", "toggle passive transfer mode", do_passive, 0 },
+	{ "lcd", "change local working directory", do_lcd, 0 },
+	{ "lpwd", "print local working directory", do_lpwd, 0 },
+	{ "put", "send one file", do_put, 1 },
+	{ "mget", "get multiple files", do_mget, 1 },
+	{ "mput", "send multiple files", do_mget, 1 },
+};
+
+static void
+cmd_interrupt(int signo)
+{
+	const char	msg[] = "\rwaiting for remote to finish abort\n";
+	int		save_errno = errno;
+
+	if (data_fp != NULL)
+		(void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
+
+	interrupted = 1;
+	errno = save_errno;
+}
+
+void
+cmd(const char *host, const char *port, const char *path)
+{
+	HistEvent	  hev;
+	EditLine	 *el;
+	History		 *hist;
+	const char	 *line;
+	char		**ap, *argv[ARGVMAX], *cp;
+	int		  count, i;
+
+	if ((el = el_init(getprogname(), stdin, stdout, stderr)) == NULL)
+		err(1, "couldn't initialise editline");
+
+	if ((hist = history_init()) == NULL)
+		err(1, "couldn't initialise editline history");
+
+	history(hist, &hev, H_SETSIZE, 100);
+	el_set(el, EL_HIST, history, hist);
+	el_set(el, EL_PROMPT, prompt);
+	el_set(el, EL_EDITOR, "emacs");
+	el_set(el, EL_TERMINAL, NULL);
+	el_set(el, EL_SIGNAL, 1);
+	el_source(el, NULL);
+
+	if (host != NULL) {
+		argv[0] = "open";
+		argv[1] = (char *)host;
+		argv[2] = port ? (char *)port : "21";
+		do_open(3, argv);
+		/* If we don't have a connection, exit */
+		if (ctrl_fp == NULL)
+			exit(1);
+
+		if (path != NULL) {
+			argv[0] = "cd";
+			argv[1] = (char *)path;
+			do_cd(2, argv);
+		}
+	}
+
+	for (;;) {
+		signal(SIGINT, SIG_IGN);
+		if ((line = el_gets(el, &count)) == NULL || count <= 0) {
+			if (verbose)
+				fprintf(stderr, "\n");
+			argv[0] = "quit";
+			do_quit(1, argv);
+			break;
+		}
+
+		if (count <= 1)
+			continue;
+
+		if ((cp = strrchr(line, '\n')) != NULL)
+			*cp = '\0';
+
+		history(hist, &hev, H_ENTER, line);
+		for (ap = argv; ap < &argv[ARGVMAX - 1] &&
+		    (*ap = strsep((char **)&line, " \t")) != NULL;) {
+			if (**ap != '\0')
+				ap++;
+		}
+		*ap = NULL;
+
+		if (argv[0] == NULL)
+			continue;
+
+		if ((i = cmd_lookup(argv[0])) == -1) {
+			fprintf(stderr, "Invalid command.\n");
+			continue;
+		}
+
+		if (cmd_tbl[i].conn_required && ctrl_fp == NULL) {
+			fprintf(stderr, "Not connected.\n");
+			continue;
+		}
+
+		interrupted = 0;
+		signal(SIGINT, cmd_interrupt);
+		cmd_tbl[i].cmd(ap - argv, argv);
+
+		if (strcmp(cmd_tbl[i].name, "quit") == 0 ||
+		    strcmp(cmd_tbl[i].name, "exit") == 0)
+			break;
+	}
+
+	el_end(el);
+}
+
+static int
+cmd_lookup(const char *cmd)
+{
+	size_t	i;
+
+	for (i = 0; i < nitems(cmd_tbl); i++)
+		if (strcmp(cmd, cmd_tbl[i].name) == 0)
+			return i;
+
+	return -1;
+}
+
+static char *
+prompt(void)
+{
+	return "ftp> ";
+}
+
+static FILE *
+data_fopen(const char *mode)
+{
+	int	 fd;
+
+	fd = activemode ? ftp_eprt(ctrl_fp) : ftp_epsv(ctrl_fp);
+	if (fd == -1) {
+		if (io_debug)
+			fprintf(stderr, "Failed to open data connection");
+
+		return NULL;
+	}
+
+	return fdopen(fd, mode);
+}
+
+static void
+ftp_abort(void)
+{
+	char	buf[BUFSIZ];
+
+	snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC);
+	if (send(fileno(ctrl_fp), buf, 3, MSG_OOB) != 3)
+		warn("abort");
+
+	ftp_command(ctrl_fp, "%cABOR", DM);
+}
+
+static void
+do_open(int argc, char **argv)
+{
+	const char	*host = NULL, *port = "21";
+	char		*buf = NULL;
+	size_t		 n = 0;
+	int		 sock;
+
+	if (ctrl_fp != NULL) {
+		fprintf(stderr, "already connected, use close first.\n");
+		return;
+	}
+
+	switch (argc) {
+	case 3:
+		port = argv[2];
+		/* FALLTHROUGH */
+	case 2:
+		host = argv[1];
+		break;
+	default:
+		fprintf(stderr, "usage: open host [port]\n");
+		return;
+	}
+
+	if ((sock = tcp_connect(host, port, 0)) == -1)
+		return;
+
+	fprintf(stderr, "Connected to %s.\n", host);
+	if ((ctrl_fp = fdopen(sock, "r+")) == NULL)
+		err(1, "%s: fdopen", __func__);
+
+	/* greeting */
+	ftp_getline(&buf, &n, 0, ctrl_fp);
+	free(buf);
+	if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) {
+		fclose(ctrl_fp);
+		ctrl_fp = NULL;
+	}
+}
+
+static void
+do_help(int argc, char **argv)
+{
+	size_t	i;
+	int	j;
+
+	if (argc == 1) {
+		for (i = 0; i < nitems(cmd_tbl); i++)
+			fprintf(stderr, "%s\n", cmd_tbl[i].name);
+
+		return;
+	}
+
+	for (i = 1; i < (size_t)argc; i++) {
+		if ((j = cmd_lookup(argv[i])) == -1)
+			fprintf(stderr, "invalid help command %s\n", argv[i]);
+		else
+			fprintf(stderr, "%s\t%s\n", argv[i], cmd_tbl[j].info);
+	}
+}
+
+static void
+do_quit(int argc, char **argv)
+{
+	if (ctrl_fp == NULL)
+		return;
+
+	ftp_command(ctrl_fp, "QUIT");
+	fclose(ctrl_fp);
+	ctrl_fp = NULL;
+}
+
+static void
+do_ls(int argc, char **argv)
+{
+	FILE		*dst_fp = stdout;
+	const char	*cmd, *local_fname = NULL, *remote_dir = NULL;
+	char		*buf = NULL;
+	size_t		 n = 0;
+	ssize_t		 len;
+	int		 r;
+
+	switch (argc) {
+	case 3:
+		if (strcmp(argv[2], "-") != 0)
+			local_fname = argv[2];
+		/* FALLTHROUGH */
+	case 2:
+		remote_dir = argv[1];
+		/* FALLTHROUGH */
+	case 1:
+		break;
+	default:
+		fprintf(stderr, "usage: ls [remote-directory [local-file]]\n");
+		return;
+	}
+
+	if ((data_fp = data_fopen("r")) == NULL)
+		return;
+
+	if (local_fname && (dst_fp = fopen(local_fname, "w")) == NULL) {
+		warn("fopen %s", local_fname);
+		fclose(data_fp);
+		data_fp = NULL;
+		return;
+	}
+
+	cmd = (strcmp(argv[0], "ls") == 0) ? "LIST" : "NLST";
+	if (remote_dir != NULL)
+		r = ftp_command(ctrl_fp, "%s %s", cmd, remote_dir);
+	else
+		r = ftp_command(ctrl_fp, "%s", cmd);
+
+	if (r != P_PRE) {
+		fclose(data_fp);
+		data_fp = NULL;
+		if (dst_fp != stdout)
+			fclose(dst_fp);
+
+		return;
+	}
+
+	while ((len = getline(&buf, &n, data_fp)) != -1 && !interrupted) {
+		buf[len - 1] = '\0';
+		if (len >= 2 && buf[len - 2] == '\r')
+			buf[len - 2] = '\0';
+
+		fprintf(dst_fp, "%s\n", buf);
+	}
+
+	if (interrupted)
+		ftp_abort();
+
+	fclose(data_fp);
+	data_fp = NULL;
+	ftp_getline(&buf, &n, 0, ctrl_fp);
+	free(buf);
+	if (dst_fp != stdout)
+		fclose(dst_fp);
+}
+
+static void
+do_get(int argc, char **argv)
+{
+	FILE		*dst_fp;
+	const char	*local_fname = NULL, *p, *remote_fname;
+	char		*buf = NULL;
+	size_t		 n = 0;
+	off_t		 file_sz, offset = 0;
+
+	switch (argc) {
+	case 3:
+		local_fname = argv[2];
+		/* FALLTHROUGH */
+	case 2:
+		remote_fname = argv[1];
+		break;
+	default:
+		fprintf(stderr, "usage: get remote-file [local-file]\n");
+		return;
+	}
+
+	if (local_fname == NULL)
+		local_fname = remote_fname;
+
+	if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
+		return;
+
+	log_info("local: %s remote: %s\n", local_fname, remote_fname);
+	if (ftp_size(ctrl_fp, remote_fname, &file_sz, &buf) != P_OK) {
+		fprintf(stderr, "%s", buf);
+		return;
+	}
+
+	if ((data_fp = data_fopen("r")) == NULL)
+		return;
+
+	if ((dst_fp = fopen(local_fname, "w")) == NULL) {
+		warn("%s", local_fname);
+		fclose(data_fp);
+		data_fp = NULL;
+		return;
+	}
+
+	if (ftp_command(ctrl_fp, "RETR %s", remote_fname) != P_PRE) {
+		fclose(data_fp);
+		data_fp = NULL;
+		fclose(dst_fp);
+		return;
+	}
+
+	if (progressmeter) {
+		p = basename(remote_fname);
+		start_progress_meter(p, NULL, file_sz, &offset);
+	}
+
+	copy_file(dst_fp, data_fp, &offset);
+	if (progressmeter)
+		stop_progress_meter();
+
+	if (interrupted)
+		ftp_abort();
+
+	fclose(data_fp);
+	data_fp = NULL;
+	fclose(dst_fp);
+	ftp_getline(&buf, &n, 0, ctrl_fp);
+	free(buf);
+}
+
+static void
+do_pwd(int argc, char **argv)
+{
+	ftp_command(ctrl_fp, "PWD");
+}
+
+static void
+do_cd(int argc, char **argv)
+{
+	if (argc != 2) {
+		fprintf(stderr, "usage: cd remote-directory\n");
+		return;
+	}
+
+	ftp_command(ctrl_fp, "CWD %s", argv[1]);
+}
+
+static void
+do_passive(int argc, char **argv)
+{
+	switch (argc) {
+	case 1:
+		break;
+	case 2:
+		if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "off") == 0)
+			break;
+
+		/* FALLTHROUGH */
+	default:
+		fprintf(stderr, "usage: passive [on | off]\n");
+		return;
+	}
+
+	if (argv[1] != NULL) {
+		activemode = (strcmp(argv[1], "off") == 0) ? 1 : 0;
+		fprintf(stderr, "passive mode is %s\n", argv[1]);
+		return;
+	}
+
+	activemode = !activemode;
+	fprintf(stderr, "passive mode is %s\n", activemode ? "off" : "on");
+}
+
+static void
+do_lcd(int argc, char **argv)
+{
+	struct passwd	*pw = NULL;
+	const char	*dir, *login;
+	char		 cwd[PATH_MAX];
+
+	switch (argc) {
+	case 1:
+	case 2:
+		break;
+	default:
+		fprintf(stderr, "usage: lcd [local-directory]\n");
+		return;
+	}
+
+	if ((login = getlogin()) != NULL)
+		pw = getpwnam(login);
+
+	if (pw == NULL && (pw = getpwuid(getuid())) == NULL) {
+		fprintf(stderr, "Failed to get home directory\n");
+		return;
+	}
+
+	dir = argv[1] ? argv[1] : pw->pw_dir;
+	if (chdir(dir) != 0) {
+		warn("local: %s", dir);
+		return;
+	}
+
+	if (getcwd(cwd, sizeof cwd) == NULL) {
+		warn("getcwd");
+		return;
+	}
+
+	fprintf(stderr, "Local directory now %s\n", cwd);
+}
+
+static void
+do_lpwd(int argc, char **argv)
+{
+	char	cwd[PATH_MAX];
+
+	if (getcwd(cwd, sizeof cwd) == NULL) {
+		warn("getcwd");
+		return;
+	}
+
+	fprintf(stderr, "Local directory %s\n", cwd);
+}
+
+static void
+do_put(int argc, char **argv)
+{
+	struct stat	 sb;
+	FILE		*src_fp;
+	const char	*local_fname, *p, *remote_fname = NULL;
+	char		*buf = NULL;
+	size_t		 n = 0;
+	off_t		 file_sz, offset = 0;
+
+	switch (argc) {
+	case 3:
+		remote_fname = argv[2];
+		/* FALLTHROUGH */
+	case 2:
+		local_fname = argv[1];
+		break;
+	default:
+		fprintf(stderr, "usage: put local-file [remote-file]\n");
+		return;
+	}
+
+	if (remote_fname == NULL)
+		remote_fname = local_fname;
+
+	if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
+		return;
+
+	log_info("local: %s remote: %s\n", local_fname, remote_fname);
+	if ((data_fp = data_fopen("w")) == NULL)
+		return;
+
+	if ((src_fp = fopen(local_fname, "r")) == NULL) {
+		warn("%s", local_fname);
+		fclose(data_fp);
+		data_fp = NULL;
+		return;
+	}
+
+	if (fstat(fileno(src_fp), &sb) != 0) {
+		warn("%s", local_fname);
+		fclose(data_fp);
+		data_fp = NULL;
+		fclose(src_fp);
+		return;
+	}
+	file_sz = sb.st_size;
+
+	if (ftp_command(ctrl_fp, "STOR %s", remote_fname) != P_PRE) {
+		fclose(data_fp);
+		data_fp = NULL;
+		fclose(src_fp);
+		return;
+	}
+
+	if (progressmeter) {
+		p = basename(remote_fname);
+		start_progress_meter(p, NULL, file_sz, &offset);
+	}
+
+	copy_file(data_fp, src_fp, &offset);
+	if (progressmeter)
+		stop_progress_meter();
+
+	if (interrupted)
+		ftp_abort();
+
+	fclose(data_fp);
+	data_fp = NULL;
+	fclose(src_fp);
+	ftp_getline(&buf, &n, 0, ctrl_fp);
+	free(buf);
+}
+
+static void
+do_mget(int argc, char **argv)
+{
+	void		(*fn)(int, char **);
+	const char	 *usage;
+	char		 *args[2];
+	int		  i;
+
+	if (strcmp(argv[0], "mget") == 0) {
+		fn = do_get;
+		args[0] = "get";
+		usage = "mget remote-files";
+	} else {
+		fn = do_put;
+		args[0] = "put";
+		usage = "mput local-files";
+	}
+
+	if (argc == 1) {
+		fprintf(stderr, "usage: %s\n", usage);
+		return;
+	}
+
+	for (i = 1; i < argc && !interrupted; i++) {
+		args[1] = argv[i];
+		fn(2, args);
+	}
+}