summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c526
1 files changed, 526 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..a3d9f67
--- /dev/null
+++ b/main.c
@@ -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);
+}