main.c
changeset 0 1d0ce1ebbc72
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Tue Dec 06 13:51:55 2022 +0000
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2015 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/cdefs.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <imsg.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ftp.h"
+#include "xmalloc.h"
+
+#define	IMSG_OPEN	 1
+
+static int		 auto_fetch(int, char **);
+static void		 child(int, int, char **);
+static int		 parent(int, pid_t);
+static struct url	*proxy_parse(const char *);
+static int		 stdout_copy(const char *);
+static int		 append(const char *);
+static int		 save(const char *);
+static int		 slurp(struct url *, FILE *, off_t *, off_t);
+static char		*output_fname(struct url *, const char *);
+static void		 re_exec(int, int, char **);
+static __dead void	 usage(void);
+static int		 read_message(struct imsgbuf *, struct imsg *);
+static void		 send_message(struct imsgbuf *, int, uint32_t, void *,
+			     size_t, int);
+
+struct url		*ftp_proxy, *http_proxy;
+const char		*useragent = "OpenBSD ftp";
+char			*oarg;
+int			 activemode, family = AF_UNSPEC, io_debug;
+int			 progressmeter, verbose = 1;
+volatile sig_atomic_t	 interrupted = 0;
+
+static struct imsgbuf	 child_ibuf;
+static const char	*title;
+static char		*tls_options;
+static int		 connect_timeout, resume;
+
+int
+main(int argc, char **argv)
+{
+	const char	 *e;
+	char		**save_argv, *term;
+	int		  ch, csock, dumb_terminal, rexec, save_argc;
+
+	if (isatty(fileno(stdin)) != 1)
+		verbose = 0;
+
+	io_debug = getenv("IO_DEBUG") != NULL;
+	term = getenv("TERM");
+	dumb_terminal = (term == NULL || *term == '\0' ||
+	    !strcmp(term, "dumb") || !strcmp(term, "emacs") ||
+	    !strcmp(term, "su"));
+	if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && !dumb_terminal)
+		progressmeter = 1;
+
+	csock = rexec = 0;
+	save_argc = argc;
+	save_argv = argv;
+	while ((ch = getopt(argc, argv,
+	    "46AaCc:dD:Eegik:MmN:no:pP:r:S:s:tU:vVw:xz:")) != -1) {
+		switch (ch) {
+		case '4':
+			family = AF_INET;
+			break;
+		case '6':
+			family = AF_INET6;
+			break;
+		case 'A':
+			activemode = 1;
+			break;
+		case 'C':
+			resume = 1;
+			break;
+		case 'D':
+			title = optarg;
+			break;
+		case 'o':
+			oarg = optarg;
+			if (!strlen(oarg))
+				oarg = NULL;
+			break;
+		case 'M':
+			progressmeter = 0;
+			break;
+		case 'm':
+			progressmeter = 1;
+			break;
+		case 'N':
+			setprogname(optarg);
+			break;
+		case 'S':
+			tls_options = optarg;
+			break;
+		case 'U':
+			useragent = optarg;
+			break;
+		case 'V':
+			verbose = 0;
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		case 'w':
+			connect_timeout = strtonum(optarg, 0, 200, &e);
+			if (e)
+				errx(1, "-w: %s", e);
+			break;
+		/* options for internal use only */
+		case 'x':
+			rexec = 1;
+			break;
+		case 'z':
+			csock = strtonum(optarg, 3, getdtablesize() - 1, &e);
+			if (e)
+				errx(1, "-z: %s", e);
+			break;
+		/* Ignoring all remaining options */
+		case 'a':
+		case 'c':
+		case 'd':
+		case 'E':
+		case 'e':
+		case 'g':
+		case 'i':
+		case 'k':
+		case 'n':
+		case 'P':
+		case 'p':
+		case 'r':
+		case 's':
+		case 't':
+			warnx("Ignoring getopt: %c", ch);
+			break;
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (rexec)
+		child(csock, argc, argv);
+
+#ifndef SMALL
+	struct url	*url;
+
+	switch (argc) {
+	case 0:
+		cmd(NULL, NULL, NULL);
+		return 0;
+	case 1:
+	case 2:
+		switch (url_scheme_lookup(argv[0])) {
+		case -1:
+			cmd(argv[0], argv[1], NULL);
+			return 0;
+		case S_FTP:
+			url = xurl_parse(argv[0]);
+			if (url->path &&
+			    url->path[strlen(url->path) - 1] != '/')
+				break; /* auto fetch */
+
+			cmd(url->host, url->port, url->path);
+			return 0;
+		}
+		break;
+	}
+#else
+	if (argc == 0)
+		usage();
+#endif /* SMALL */
+
+	return auto_fetch(save_argc, save_argv);
+}
+
+static int
+auto_fetch(int sargc, char **sargv)
+{
+	pid_t	pid;
+	int	sp[2];
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) != 0)
+		err(1, "socketpair");
+
+	switch (pid = fork()) {
+	case -1:
+		err(1, "fork");
+	case 0:
+		close(sp[0]);
+		re_exec(sp[1], sargc, sargv);
+	}
+
+	close(sp[1]);
+	return parent(sp[0], pid);
+}
+
+static void
+re_exec(int sock, int argc, char **argv)
+{
+	char	**nargv, *sock_str;
+	int	  i, j, nargc;
+
+	nargc = argc + 4;
+	nargv = xcalloc(nargc, sizeof(*nargv));
+	xasprintf(&sock_str, "%d", sock);
+	i = 0;
+	nargv[i++] = argv[0];
+	nargv[i++] = "-z";
+	nargv[i++] = sock_str;
+	nargv[i++] = "-x";
+	for (j = 1; j < argc; j++)
+		nargv[i++] = argv[j];
+
+	execvp(nargv[0], nargv);
+	err(1, "execvp");
+}
+
+static int
+parent(int sock, pid_t child_pid)
+{
+	struct imsgbuf	ibuf;
+	struct imsg	imsg;
+	struct stat	sb;
+	off_t		offset;
+	int		fd, save_errno, sig, status;
+
+	setproctitle("%s", "parent");
+	if (pledge("stdio cpath rpath wpath sendfd", NULL) == -1)
+		err(1, "pledge");
+
+	imsg_init(&ibuf, sock);
+	for (;;) {
+		if (read_message(&ibuf, &imsg) == 0)
+			break;
+
+		if (imsg.hdr.type != IMSG_OPEN)
+			errx(1, "%s: IMSG_OPEN expected", __func__);
+
+		offset = 0;
+		fd = open(imsg.data, imsg.hdr.peerid, 0666);
+		save_errno = errno;
+		if (fd != -1 && fstat(fd, &sb) == 0) {
+			if (sb.st_mode & S_IFDIR) {
+				close(fd);
+				fd = -1;
+				save_errno = EISDIR;
+			} else
+				offset = sb.st_size;
+		}
+
+		send_message(&ibuf, IMSG_OPEN, save_errno,
+		    &offset, sizeof offset, fd);
+		imsg_free(&imsg);
+	}
+
+	close(sock);
+	if (waitpid(child_pid, &status, 0) == -1 && errno != ECHILD)
+		err(1, "wait");
+
+	sig = WTERMSIG(status);
+	if (WIFSIGNALED(status) && sig != SIGPIPE)
+		errx(1, "child terminated: signal %d", sig);
+
+	return WEXITSTATUS(status);
+}
+
+static void
+child(int sock, int argc, char **argv)
+{
+	int	i, to_stdout = 0, r = 0;
+
+	setproctitle("%s", "child");
+
+#ifndef NOSSL
+	/*
+	 * TLS can't be init-ed on first use as filesystem(ca file) isn't
+	 * available after pledge(2).
+	 */
+	https_init(tls_options);
+#endif /* NOSSL */
+
+	if (pledge("stdio inet dns recvfd tty unveil", NULL) == -1)
+		err(1, "pledge");
+	if (!progressmeter &&
+	    pledge("stdio inet dns recvfd unveil", NULL) == -1)
+		err(1, "pledge");
+
+	imsg_init(&child_ibuf, sock);
+	ftp_proxy = proxy_parse("ftp_proxy");
+	http_proxy = proxy_parse("http_proxy");
+
+	if (oarg) {
+		if (strcmp(oarg, "-") == 0) {
+			to_stdout = 1;
+			if (resume)
+				errx(1, "can't append to stdout");
+		} else if (unveil(oarg, "w") == -1)
+			err(1, "unveil");
+
+		if (unveil(NULL, NULL) == -1)
+			err(1, "unveil");
+	}
+
+	for (i = 0; i < argc; i++) {
+		if (to_stdout)
+			r = stdout_copy(argv[i]);
+		else if (resume)
+			r = append(argv[i]);
+		else
+			r = save(argv[i]);
+	}
+
+	exit(r);
+}
+
+static int
+stdout_copy(const char *arg)
+{
+	struct url	*url;
+	off_t		 offset = 0, sz = 0;
+
+	url = xurl_parse(arg);
+	url_connect(url, connect_timeout);
+	url = url_request(url, &offset, &sz);
+	return slurp(url, stdout, &offset, sz);
+}
+
+static int
+append(const char *arg)
+{
+	struct url	*url;
+	FILE		*fp;
+	char		*fname;
+	off_t		 offset = 0, sz = 0;
+	int		 fd;
+
+	url = xurl_parse(arg);
+	url_connect(url, connect_timeout);
+	fname = output_fname(url, arg);
+	fd = fd_request(fname, O_WRONLY|O_APPEND, &offset);
+	url = url_request(url, &offset, &sz);
+	/* If HTTP server doesn't support range requests, truncate. */
+	if (fd != -1 && offset == 0)
+		if (ftruncate(fd, 0) != 0)
+			err(1, "ftruncate");
+
+	if (fd == -1 &&
+	    (fd = fd_request(fname, O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)
+		err(1, "Can't open file %s", fname);
+
+	if ((fp = fdopen(fd, "w")) == NULL)
+		err(1, "%s: fdopen", __func__);
+
+	return slurp(url, fp, &offset, sz);
+}
+
+static int
+save(const char *arg)
+{
+	struct url	*url;
+	FILE		*fp;
+	char		*fname;
+	off_t		 offset = 0, sz = 0;
+	int		 fd, r;
+
+	url = xurl_parse(arg);
+	url_connect(url, connect_timeout);
+	url = url_request(url, &offset, &sz);
+	fname = output_fname(url, arg);
+	if ((fd = fd_request(fname, O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)
+		err(1, "Can't open file %s", fname);
+
+	if ((fp = fdopen(fd, "w")) == NULL)
+		err(1, "%s: fdopen", __func__);
+
+	return slurp(url, fp, &offset, sz);
+}
+
+static int
+slurp(struct url *url, FILE *fp, off_t *offset, off_t sz)
+{
+	start_progress_meter(basename(url->path), title, sz, offset);
+	url_save(url, fp, offset);
+	stop_progress_meter();
+	url_close(url);
+	url_free(url);
+	if (fp != stdout)
+		fclose(fp);
+
+	if (sz != 0 && *offset != sz) {
+		log_info("Read short file\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static char *
+output_fname(struct url *url, const char *arg)
+{
+	char	*fname;
+
+	fname = oarg ? oarg : basename(url->path);
+	if (strcmp(fname, "/") == 0)
+		errx(1, "No filename after host (use -o): %s", arg);
+
+	if (strcmp(fname, ".") == 0)
+		errx(1, "No '/' after host (use -o): %s", arg);
+
+	return fname;
+}
+
+int
+fd_request(char *path, int flags, off_t *offset)
+{
+	struct imsg	 imsg;
+	off_t		*poffset;
+	int		 fd, save_errno;
+
+	send_message(&child_ibuf, IMSG_OPEN, flags, path, strlen(path) + 1, -1);
+	if (read_message(&child_ibuf, &imsg) == 0)
+		return -1;
+
+	if (imsg.hdr.type != IMSG_OPEN)
+		errx(1, "%s: IMSG_OPEN expected", __func__);
+
+	fd = imsg.fd;
+	if (offset) {
+		poffset = imsg.data;
+		*offset = *poffset;
+	}
+
+	save_errno = imsg.hdr.peerid;
+	imsg_free(&imsg);
+	errno = save_errno;
+	return fd;
+}
+
+void
+send_message(struct imsgbuf *ibuf, int type, uint32_t peerid,
+    void *msg, size_t msglen, int fd)
+{
+	if (imsg_compose(ibuf, type, peerid, 0, fd, msg, msglen) != 1)
+		err(1, "imsg_compose");
+
+	if (imsg_flush(ibuf) != 0)
+		err(1, "imsg_flush");
+}
+
+int
+read_message(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+	int	n;
+
+	if ((n = imsg_read(ibuf)) == -1)
+		err(1, "%s: imsg_read", __func__);
+	if (n == 0)
+		return 0;
+
+	if ((n = imsg_get(ibuf, imsg)) == -1)
+		err(1, "%s: imsg_get", __func__);
+	if (n == 0)
+		return 0;
+
+	return n;
+}
+
+static struct url *
+proxy_parse(const char *name)
+{
+	struct url	*proxy;
+	char		*str;
+
+	if ((str = getenv(name)) == NULL)
+		return NULL;
+
+	if (strlen(str) == 0)
+		return NULL;
+
+	proxy = xurl_parse(str);
+	if (proxy->scheme != S_HTTP)
+		errx(1, "Malformed proxy URL: %s", str);
+
+	return proxy;
+}
+
+static __dead void
+usage(void)
+{
+	fprintf(stderr,
+	    "usage:\t%s [-46AVv] [-D title] [host [port]]\n"
+	    "\t%s [-46ACVMmVv] [-N name] [-D title] [-o output]\n"
+	    "\t\t [-S tls_options] [-U useragent] [-w seconds] url ...\n",
+	    getprogname(), getprogname());
+
+	exit(1);
+}