/* * Copyright (c) 2015 Sunil Nimmagadda * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }