main.c
changeset 0 1d0ce1ebbc72
equal deleted inserted replaced
-1:000000000000 0:1d0ce1ebbc72
       
     1 /*
       
     2  * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
       
     3  *
       
     4  * Permission to use, copy, modify, and distribute this software for any
       
     5  * purpose with or without fee is hereby granted, provided that the above
       
     6  * copyright notice and this permission notice appear in all copies.
       
     7  *
       
     8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       
     9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       
    10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       
    11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       
    12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       
    13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       
    14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       
    15  */
       
    16 
       
    17 #include <sys/cdefs.h>
       
    18 #include <sys/types.h>
       
    19 #include <sys/queue.h>
       
    20 #include <sys/stat.h>
       
    21 #include <sys/socket.h>
       
    22 #include <sys/wait.h>
       
    23 
       
    24 #include <err.h>
       
    25 #include <errno.h>
       
    26 #include <fcntl.h>
       
    27 #include <imsg.h>
       
    28 #include <libgen.h>
       
    29 #include <signal.h>
       
    30 #include <stdio.h>
       
    31 #include <stdlib.h>
       
    32 #include <string.h>
       
    33 #include <unistd.h>
       
    34 
       
    35 #include "ftp.h"
       
    36 #include "xmalloc.h"
       
    37 
       
    38 #define	IMSG_OPEN	 1
       
    39 
       
    40 static int		 auto_fetch(int, char **);
       
    41 static void		 child(int, int, char **);
       
    42 static int		 parent(int, pid_t);
       
    43 static struct url	*proxy_parse(const char *);
       
    44 static int		 stdout_copy(const char *);
       
    45 static int		 append(const char *);
       
    46 static int		 save(const char *);
       
    47 static int		 slurp(struct url *, FILE *, off_t *, off_t);
       
    48 static char		*output_fname(struct url *, const char *);
       
    49 static void		 re_exec(int, int, char **);
       
    50 static __dead void	 usage(void);
       
    51 static int		 read_message(struct imsgbuf *, struct imsg *);
       
    52 static void		 send_message(struct imsgbuf *, int, uint32_t, void *,
       
    53 			     size_t, int);
       
    54 
       
    55 struct url		*ftp_proxy, *http_proxy;
       
    56 const char		*useragent = "OpenBSD ftp";
       
    57 char			*oarg;
       
    58 int			 activemode, family = AF_UNSPEC, io_debug;
       
    59 int			 progressmeter, verbose = 1;
       
    60 volatile sig_atomic_t	 interrupted = 0;
       
    61 
       
    62 static struct imsgbuf	 child_ibuf;
       
    63 static const char	*title;
       
    64 static char		*tls_options;
       
    65 static int		 connect_timeout, resume;
       
    66 
       
    67 int
       
    68 main(int argc, char **argv)
       
    69 {
       
    70 	const char	 *e;
       
    71 	char		**save_argv, *term;
       
    72 	int		  ch, csock, dumb_terminal, rexec, save_argc;
       
    73 
       
    74 	if (isatty(fileno(stdin)) != 1)
       
    75 		verbose = 0;
       
    76 
       
    77 	io_debug = getenv("IO_DEBUG") != NULL;
       
    78 	term = getenv("TERM");
       
    79 	dumb_terminal = (term == NULL || *term == '\0' ||
       
    80 	    !strcmp(term, "dumb") || !strcmp(term, "emacs") ||
       
    81 	    !strcmp(term, "su"));
       
    82 	if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && !dumb_terminal)
       
    83 		progressmeter = 1;
       
    84 
       
    85 	csock = rexec = 0;
       
    86 	save_argc = argc;
       
    87 	save_argv = argv;
       
    88 	while ((ch = getopt(argc, argv,
       
    89 	    "46AaCc:dD:Eegik:MmN:no:pP:r:S:s:tU:vVw:xz:")) != -1) {
       
    90 		switch (ch) {
       
    91 		case '4':
       
    92 			family = AF_INET;
       
    93 			break;
       
    94 		case '6':
       
    95 			family = AF_INET6;
       
    96 			break;
       
    97 		case 'A':
       
    98 			activemode = 1;
       
    99 			break;
       
   100 		case 'C':
       
   101 			resume = 1;
       
   102 			break;
       
   103 		case 'D':
       
   104 			title = optarg;
       
   105 			break;
       
   106 		case 'o':
       
   107 			oarg = optarg;
       
   108 			if (!strlen(oarg))
       
   109 				oarg = NULL;
       
   110 			break;
       
   111 		case 'M':
       
   112 			progressmeter = 0;
       
   113 			break;
       
   114 		case 'm':
       
   115 			progressmeter = 1;
       
   116 			break;
       
   117 		case 'N':
       
   118 			setprogname(optarg);
       
   119 			break;
       
   120 		case 'S':
       
   121 			tls_options = optarg;
       
   122 			break;
       
   123 		case 'U':
       
   124 			useragent = optarg;
       
   125 			break;
       
   126 		case 'V':
       
   127 			verbose = 0;
       
   128 			break;
       
   129 		case 'v':
       
   130 			verbose = 1;
       
   131 			break;
       
   132 		case 'w':
       
   133 			connect_timeout = strtonum(optarg, 0, 200, &e);
       
   134 			if (e)
       
   135 				errx(1, "-w: %s", e);
       
   136 			break;
       
   137 		/* options for internal use only */
       
   138 		case 'x':
       
   139 			rexec = 1;
       
   140 			break;
       
   141 		case 'z':
       
   142 			csock = strtonum(optarg, 3, getdtablesize() - 1, &e);
       
   143 			if (e)
       
   144 				errx(1, "-z: %s", e);
       
   145 			break;
       
   146 		/* Ignoring all remaining options */
       
   147 		case 'a':
       
   148 		case 'c':
       
   149 		case 'd':
       
   150 		case 'E':
       
   151 		case 'e':
       
   152 		case 'g':
       
   153 		case 'i':
       
   154 		case 'k':
       
   155 		case 'n':
       
   156 		case 'P':
       
   157 		case 'p':
       
   158 		case 'r':
       
   159 		case 's':
       
   160 		case 't':
       
   161 			warnx("Ignoring getopt: %c", ch);
       
   162 			break;
       
   163 		default:
       
   164 			usage();
       
   165 		}
       
   166 	}
       
   167 	argc -= optind;
       
   168 	argv += optind;
       
   169 
       
   170 	if (rexec)
       
   171 		child(csock, argc, argv);
       
   172 
       
   173 #ifndef SMALL
       
   174 	struct url	*url;
       
   175 
       
   176 	switch (argc) {
       
   177 	case 0:
       
   178 		cmd(NULL, NULL, NULL);
       
   179 		return 0;
       
   180 	case 1:
       
   181 	case 2:
       
   182 		switch (url_scheme_lookup(argv[0])) {
       
   183 		case -1:
       
   184 			cmd(argv[0], argv[1], NULL);
       
   185 			return 0;
       
   186 		case S_FTP:
       
   187 			url = xurl_parse(argv[0]);
       
   188 			if (url->path &&
       
   189 			    url->path[strlen(url->path) - 1] != '/')
       
   190 				break; /* auto fetch */
       
   191 
       
   192 			cmd(url->host, url->port, url->path);
       
   193 			return 0;
       
   194 		}
       
   195 		break;
       
   196 	}
       
   197 #else
       
   198 	if (argc == 0)
       
   199 		usage();
       
   200 #endif /* SMALL */
       
   201 
       
   202 	return auto_fetch(save_argc, save_argv);
       
   203 }
       
   204 
       
   205 static int
       
   206 auto_fetch(int sargc, char **sargv)
       
   207 {
       
   208 	pid_t	pid;
       
   209 	int	sp[2];
       
   210 
       
   211 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) != 0)
       
   212 		err(1, "socketpair");
       
   213 
       
   214 	switch (pid = fork()) {
       
   215 	case -1:
       
   216 		err(1, "fork");
       
   217 	case 0:
       
   218 		close(sp[0]);
       
   219 		re_exec(sp[1], sargc, sargv);
       
   220 	}
       
   221 
       
   222 	close(sp[1]);
       
   223 	return parent(sp[0], pid);
       
   224 }
       
   225 
       
   226 static void
       
   227 re_exec(int sock, int argc, char **argv)
       
   228 {
       
   229 	char	**nargv, *sock_str;
       
   230 	int	  i, j, nargc;
       
   231 
       
   232 	nargc = argc + 4;
       
   233 	nargv = xcalloc(nargc, sizeof(*nargv));
       
   234 	xasprintf(&sock_str, "%d", sock);
       
   235 	i = 0;
       
   236 	nargv[i++] = argv[0];
       
   237 	nargv[i++] = "-z";
       
   238 	nargv[i++] = sock_str;
       
   239 	nargv[i++] = "-x";
       
   240 	for (j = 1; j < argc; j++)
       
   241 		nargv[i++] = argv[j];
       
   242 
       
   243 	execvp(nargv[0], nargv);
       
   244 	err(1, "execvp");
       
   245 }
       
   246 
       
   247 static int
       
   248 parent(int sock, pid_t child_pid)
       
   249 {
       
   250 	struct imsgbuf	ibuf;
       
   251 	struct imsg	imsg;
       
   252 	struct stat	sb;
       
   253 	off_t		offset;
       
   254 	int		fd, save_errno, sig, status;
       
   255 
       
   256 	setproctitle("%s", "parent");
       
   257 	if (pledge("stdio cpath rpath wpath sendfd", NULL) == -1)
       
   258 		err(1, "pledge");
       
   259 
       
   260 	imsg_init(&ibuf, sock);
       
   261 	for (;;) {
       
   262 		if (read_message(&ibuf, &imsg) == 0)
       
   263 			break;
       
   264 
       
   265 		if (imsg.hdr.type != IMSG_OPEN)
       
   266 			errx(1, "%s: IMSG_OPEN expected", __func__);
       
   267 
       
   268 		offset = 0;
       
   269 		fd = open(imsg.data, imsg.hdr.peerid, 0666);
       
   270 		save_errno = errno;
       
   271 		if (fd != -1 && fstat(fd, &sb) == 0) {
       
   272 			if (sb.st_mode & S_IFDIR) {
       
   273 				close(fd);
       
   274 				fd = -1;
       
   275 				save_errno = EISDIR;
       
   276 			} else
       
   277 				offset = sb.st_size;
       
   278 		}
       
   279 
       
   280 		send_message(&ibuf, IMSG_OPEN, save_errno,
       
   281 		    &offset, sizeof offset, fd);
       
   282 		imsg_free(&imsg);
       
   283 	}
       
   284 
       
   285 	close(sock);
       
   286 	if (waitpid(child_pid, &status, 0) == -1 && errno != ECHILD)
       
   287 		err(1, "wait");
       
   288 
       
   289 	sig = WTERMSIG(status);
       
   290 	if (WIFSIGNALED(status) && sig != SIGPIPE)
       
   291 		errx(1, "child terminated: signal %d", sig);
       
   292 
       
   293 	return WEXITSTATUS(status);
       
   294 }
       
   295 
       
   296 static void
       
   297 child(int sock, int argc, char **argv)
       
   298 {
       
   299 	int	i, to_stdout = 0, r = 0;
       
   300 
       
   301 	setproctitle("%s", "child");
       
   302 
       
   303 #ifndef NOSSL
       
   304 	/*
       
   305 	 * TLS can't be init-ed on first use as filesystem(ca file) isn't
       
   306 	 * available after pledge(2).
       
   307 	 */
       
   308 	https_init(tls_options);
       
   309 #endif /* NOSSL */
       
   310 
       
   311 	if (pledge("stdio inet dns recvfd tty unveil", NULL) == -1)
       
   312 		err(1, "pledge");
       
   313 	if (!progressmeter &&
       
   314 	    pledge("stdio inet dns recvfd unveil", NULL) == -1)
       
   315 		err(1, "pledge");
       
   316 
       
   317 	imsg_init(&child_ibuf, sock);
       
   318 	ftp_proxy = proxy_parse("ftp_proxy");
       
   319 	http_proxy = proxy_parse("http_proxy");
       
   320 
       
   321 	if (oarg) {
       
   322 		if (strcmp(oarg, "-") == 0) {
       
   323 			to_stdout = 1;
       
   324 			if (resume)
       
   325 				errx(1, "can't append to stdout");
       
   326 		} else if (unveil(oarg, "w") == -1)
       
   327 			err(1, "unveil");
       
   328 
       
   329 		if (unveil(NULL, NULL) == -1)
       
   330 			err(1, "unveil");
       
   331 	}
       
   332 
       
   333 	for (i = 0; i < argc; i++) {
       
   334 		if (to_stdout)
       
   335 			r = stdout_copy(argv[i]);
       
   336 		else if (resume)
       
   337 			r = append(argv[i]);
       
   338 		else
       
   339 			r = save(argv[i]);
       
   340 	}
       
   341 
       
   342 	exit(r);
       
   343 }
       
   344 
       
   345 static int
       
   346 stdout_copy(const char *arg)
       
   347 {
       
   348 	struct url	*url;
       
   349 	off_t		 offset = 0, sz = 0;
       
   350 
       
   351 	url = xurl_parse(arg);
       
   352 	url_connect(url, connect_timeout);
       
   353 	url = url_request(url, &offset, &sz);
       
   354 	return slurp(url, stdout, &offset, sz);
       
   355 }
       
   356 
       
   357 static int
       
   358 append(const char *arg)
       
   359 {
       
   360 	struct url	*url;
       
   361 	FILE		*fp;
       
   362 	char		*fname;
       
   363 	off_t		 offset = 0, sz = 0;
       
   364 	int		 fd;
       
   365 
       
   366 	url = xurl_parse(arg);
       
   367 	url_connect(url, connect_timeout);
       
   368 	fname = output_fname(url, arg);
       
   369 	fd = fd_request(fname, O_WRONLY|O_APPEND, &offset);
       
   370 	url = url_request(url, &offset, &sz);
       
   371 	/* If HTTP server doesn't support range requests, truncate. */
       
   372 	if (fd != -1 && offset == 0)
       
   373 		if (ftruncate(fd, 0) != 0)
       
   374 			err(1, "ftruncate");
       
   375 
       
   376 	if (fd == -1 &&
       
   377 	    (fd = fd_request(fname, O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)
       
   378 		err(1, "Can't open file %s", fname);
       
   379 
       
   380 	if ((fp = fdopen(fd, "w")) == NULL)
       
   381 		err(1, "%s: fdopen", __func__);
       
   382 
       
   383 	return slurp(url, fp, &offset, sz);
       
   384 }
       
   385 
       
   386 static int
       
   387 save(const char *arg)
       
   388 {
       
   389 	struct url	*url;
       
   390 	FILE		*fp;
       
   391 	char		*fname;
       
   392 	off_t		 offset = 0, sz = 0;
       
   393 	int		 fd, r;
       
   394 
       
   395 	url = xurl_parse(arg);
       
   396 	url_connect(url, connect_timeout);
       
   397 	url = url_request(url, &offset, &sz);
       
   398 	fname = output_fname(url, arg);
       
   399 	if ((fd = fd_request(fname, O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)
       
   400 		err(1, "Can't open file %s", fname);
       
   401 
       
   402 	if ((fp = fdopen(fd, "w")) == NULL)
       
   403 		err(1, "%s: fdopen", __func__);
       
   404 
       
   405 	return slurp(url, fp, &offset, sz);
       
   406 }
       
   407 
       
   408 static int
       
   409 slurp(struct url *url, FILE *fp, off_t *offset, off_t sz)
       
   410 {
       
   411 	start_progress_meter(basename(url->path), title, sz, offset);
       
   412 	url_save(url, fp, offset);
       
   413 	stop_progress_meter();
       
   414 	url_close(url);
       
   415 	url_free(url);
       
   416 	if (fp != stdout)
       
   417 		fclose(fp);
       
   418 
       
   419 	if (sz != 0 && *offset != sz) {
       
   420 		log_info("Read short file\n");
       
   421 		return 1;
       
   422 	}
       
   423 
       
   424 	return 0;
       
   425 }
       
   426 
       
   427 static char *
       
   428 output_fname(struct url *url, const char *arg)
       
   429 {
       
   430 	char	*fname;
       
   431 
       
   432 	fname = oarg ? oarg : basename(url->path);
       
   433 	if (strcmp(fname, "/") == 0)
       
   434 		errx(1, "No filename after host (use -o): %s", arg);
       
   435 
       
   436 	if (strcmp(fname, ".") == 0)
       
   437 		errx(1, "No '/' after host (use -o): %s", arg);
       
   438 
       
   439 	return fname;
       
   440 }
       
   441 
       
   442 int
       
   443 fd_request(char *path, int flags, off_t *offset)
       
   444 {
       
   445 	struct imsg	 imsg;
       
   446 	off_t		*poffset;
       
   447 	int		 fd, save_errno;
       
   448 
       
   449 	send_message(&child_ibuf, IMSG_OPEN, flags, path, strlen(path) + 1, -1);
       
   450 	if (read_message(&child_ibuf, &imsg) == 0)
       
   451 		return -1;
       
   452 
       
   453 	if (imsg.hdr.type != IMSG_OPEN)
       
   454 		errx(1, "%s: IMSG_OPEN expected", __func__);
       
   455 
       
   456 	fd = imsg.fd;
       
   457 	if (offset) {
       
   458 		poffset = imsg.data;
       
   459 		*offset = *poffset;
       
   460 	}
       
   461 
       
   462 	save_errno = imsg.hdr.peerid;
       
   463 	imsg_free(&imsg);
       
   464 	errno = save_errno;
       
   465 	return fd;
       
   466 }
       
   467 
       
   468 void
       
   469 send_message(struct imsgbuf *ibuf, int type, uint32_t peerid,
       
   470     void *msg, size_t msglen, int fd)
       
   471 {
       
   472 	if (imsg_compose(ibuf, type, peerid, 0, fd, msg, msglen) != 1)
       
   473 		err(1, "imsg_compose");
       
   474 
       
   475 	if (imsg_flush(ibuf) != 0)
       
   476 		err(1, "imsg_flush");
       
   477 }
       
   478 
       
   479 int
       
   480 read_message(struct imsgbuf *ibuf, struct imsg *imsg)
       
   481 {
       
   482 	int	n;
       
   483 
       
   484 	if ((n = imsg_read(ibuf)) == -1)
       
   485 		err(1, "%s: imsg_read", __func__);
       
   486 	if (n == 0)
       
   487 		return 0;
       
   488 
       
   489 	if ((n = imsg_get(ibuf, imsg)) == -1)
       
   490 		err(1, "%s: imsg_get", __func__);
       
   491 	if (n == 0)
       
   492 		return 0;
       
   493 
       
   494 	return n;
       
   495 }
       
   496 
       
   497 static struct url *
       
   498 proxy_parse(const char *name)
       
   499 {
       
   500 	struct url	*proxy;
       
   501 	char		*str;
       
   502 
       
   503 	if ((str = getenv(name)) == NULL)
       
   504 		return NULL;
       
   505 
       
   506 	if (strlen(str) == 0)
       
   507 		return NULL;
       
   508 
       
   509 	proxy = xurl_parse(str);
       
   510 	if (proxy->scheme != S_HTTP)
       
   511 		errx(1, "Malformed proxy URL: %s", str);
       
   512 
       
   513 	return proxy;
       
   514 }
       
   515 
       
   516 static __dead void
       
   517 usage(void)
       
   518 {
       
   519 	fprintf(stderr,
       
   520 	    "usage:\t%s [-46AVv] [-D title] [host [port]]\n"
       
   521 	    "\t%s [-46ACVMmVv] [-N name] [-D title] [-o output]\n"
       
   522 	    "\t\t [-S tls_options] [-U useragent] [-w seconds] url ...\n",
       
   523 	    getprogname(), getprogname());
       
   524 
       
   525 	exit(1);
       
   526 }