util.c
author Sunil Nimmagadda <sunil@nimmagadda.net>
Tue, 06 Dec 2022 13:51:55 +0000
changeset 0 1d0ce1ebbc72
permissions -rw-r--r--
An HTTP(S), FTP client. Found a copy of some old OpenBSD days hacking stashed somewhere in the backups. This version saw the light of the day as official OpenBSD ftp(1) for a grand total of 1 day :-)

/*
 * 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/socket.h>

#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "ftp.h"
#include "xmalloc.h"

static void	tooslow(int);

/*
 * Wait for an asynchronous connect(2) attempt to finish.
 */
int
connect_wait(int s)
{
	struct pollfd pfd[1];
	int error = 0;
	socklen_t len = sizeof(error);

	pfd[0].fd = s;
	pfd[0].events = POLLOUT;

	if (poll(pfd, 1, -1) == -1)
		return -1;
	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
		return -1;
	if (error != 0) {
		errno = error;
		return -1;
	}
	return 0;
}

static void
tooslow(int signo)
{
	dprintf(STDERR_FILENO, "%s: connect taking too long\n", getprogname());
	_exit(2);
}

int
tcp_connect(const char *host, const char *port, int timeout)
{
	struct addrinfo	 hints, *res, *res0;
	char		 hbuf[NI_MAXHOST];
	const char	*cause = NULL;
	int		 error, s = -1, save_errno;

	if (host == NULL) {
		warnx("hostname missing");
		return -1;
	}

	memset(&hints, 0, sizeof hints);
	hints.ai_family = family;
	hints.ai_socktype = SOCK_STREAM;
	if ((error = getaddrinfo(host, port, &hints, &res0))) {
		warnx("%s: %s", host, gai_strerror(error));
		return -1;
	}

	if (timeout) {
		(void)signal(SIGALRM, tooslow);
		alarm(timeout);
	}

	for (res = res0; res; res = res->ai_next) {
		if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
		    sizeof hbuf, NULL, 0, NI_NUMERICHOST) != 0)
			(void)strlcpy(hbuf, "(unknown)", sizeof hbuf);

		log_info("Trying %s...\n", hbuf);
		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (s == -1) {
			cause = "socket";
			continue;
		}

		for (error = connect(s, res->ai_addr, res->ai_addrlen);
		    error != 0 && errno == EINTR; error = connect_wait(s))
			continue;

		if (error != 0) {
			cause = "connect";
			save_errno = errno;
			close(s);
			errno = save_errno;
			s = -1;
			continue;
		}

		break;
	}

	freeaddrinfo(res0);
	if (s == -1) {
		warn("%s", cause);
		return -1;
	}

	if (timeout) {
		signal(SIGALRM, SIG_DFL);
		alarm(0);
	}

	return s;
}

void
log_info(const char *fmt, ...)
{
	va_list	ap;

	if (verbose == 0)
		return;

	va_start(ap, fmt);
	if (oarg && strcmp(oarg, "-") == 0)
		vfprintf(stderr, fmt, ap);
	else
		vprintf(fmt, ap);

	va_end(ap);
}

void
log_request(const char *prefix, struct url *url, struct url *proxy)
{
	char	*host;
	int	 custom_port;

	if (url->scheme == S_FILE)
		return;

	custom_port = strcmp(url->port, url_port_str(url->scheme)) ? 1 : 0;
	if (url->ip_literal)
		xasprintf(&host, "[%s]", url->host);
	else
		host = xstrdup(url->host);

	if (proxy)
		log_info("%s %s//%s%s%s%s"
		    " (via %s//%s%s%s)\n",
		    prefix,
		    url_scheme_str(url->scheme),
		    host,
		    custom_port ? ":" : "",
		    custom_port ? url->port : "",
		    url->path ? url->path : "",

		    /* via proxy part */
		    (proxy->scheme == S_HTTP) ? "http" : "https",
		    proxy->host,
		    proxy->port ? ":" : "",
		    proxy->port ? proxy->port : "");
	else
		log_info("%s %s//%s%s%s%s\n",
		    prefix,
		    url_scheme_str(url->scheme),
		    host,
		    custom_port ? ":" : "",
		    custom_port ? url->port : "",
		    url->path ? url->path : "");

	free(host);
}

void
copy_file(FILE *dst, FILE *src, off_t *offset)
{
	char	*tmp_buf;
	size_t	 r;

	tmp_buf = xmalloc(TMPBUF_LEN);
	while ((r = fread(tmp_buf, 1, TMPBUF_LEN, src)) != 0 && !interrupted) {
		*offset += r;
		if (fwrite(tmp_buf, 1, r, dst) != r)
			err(1, "%s: fwrite", __func__);
	}

	if (interrupted) {
		free(tmp_buf);
		return;
	}

	if (!feof(src))
		errx(1, "%s: fread", __func__);

	free(tmp_buf);
}