From 8f888fb1a3469b87e557efad93b293dd36288ba9 Mon Sep 17 00:00:00 2001 From: Sunil Nimmagadda Date: Sat, 14 Sep 2024 12:32:01 +0530 Subject: A HTTP(S), FTP client --- main.c | 526 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 main.c (limited to 'main.c') 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 + * + * 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); +} -- cgit v1.2.3