Blame src/curl_stream.c

Packit Service 20376f
/*
Packit Service 20376f
 * Copyright (C) the libgit2 contributors. All rights reserved.
Packit Service 20376f
 *
Packit Service 20376f
 * This file is part of libgit2, distributed under the GNU GPL v2 with
Packit Service 20376f
 * a Linking Exception. For full terms see the included COPYING file.
Packit Service 20376f
 */
Packit Service 20376f
Packit Service 20376f
#ifdef GIT_CURL
Packit Service 20376f
Packit Service 20376f
#include <curl/curl.h>
Packit Service 20376f
Packit Service 20376f
#include "stream.h"
Packit Service 20376f
#include "git2/transport.h"
Packit Service 20376f
#include "buffer.h"
Packit Service 20376f
#include "global.h"
Packit Service 20376f
#include "vector.h"
Packit Service 20376f
#include "proxy.h"
Packit Service 20376f
Packit Service 20376f
/* This is for backwards compatibility with curl<7.45.0. */
Packit Service 20376f
#ifndef CURLINFO_ACTIVESOCKET
Packit Service 20376f
# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET
Packit Service 20376f
# define GIT_CURL_BADSOCKET -1
Packit Service 20376f
# define git_activesocket_t long
Packit Service 20376f
#else
Packit Service 20376f
# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD
Packit Service 20376f
# define git_activesocket_t curl_socket_t
Packit Service 20376f
#endif
Packit Service 20376f
Packit Service 20376f
typedef struct {
Packit Service 20376f
	git_stream parent;
Packit Service 20376f
	CURL *handle;
Packit Service 20376f
	curl_socket_t socket;
Packit Service 20376f
	char curl_error[CURL_ERROR_SIZE + 1];
Packit Service 20376f
	git_cert_x509 cert_info;
Packit Service 20376f
	git_strarray cert_info_strings;
Packit Service 20376f
	git_proxy_options proxy;
Packit Service 20376f
	git_cred *proxy_cred;
Packit Service 20376f
} curl_stream;
Packit Service 20376f
Packit Service 20376f
int git_curl_stream_global_init(void)
Packit Service 20376f
{
Packit Service 20376f
	if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
Packit Service 20376f
		giterr_set(GITERR_NET, "could not initialize curl");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	/* `curl_global_cleanup` is provided by libcurl */
Packit Service 20376f
	git__on_shutdown(curl_global_cleanup);
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int seterr_curl(curl_stream *s)
Packit Service 20376f
{
Packit Service 20376f
	giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
Packit Service 20376f
	return -1;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
GIT_INLINE(int) error_no_credentials(void)
Packit Service 20376f
{
Packit Service 20376f
	giterr_set(GITERR_NET, "proxy authentication required, but no callback provided");
Packit Service 20376f
	return GIT_EAUTH;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int apply_proxy_creds(curl_stream *s)
Packit Service 20376f
{
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
	git_cred_userpass_plaintext *userpass;
Packit Service 20376f
Packit Service 20376f
	if (!s->proxy_cred)
Packit Service 20376f
		return GIT_ENOTFOUND;
Packit Service 20376f
Packit Service 20376f
	userpass = (git_cred_userpass_plaintext *) s->proxy_cred;
Packit Service 20376f
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int ask_and_apply_proxy_creds(curl_stream *s)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	git_proxy_options *opts = &s->proxy;
Packit Service 20376f
Packit Service 20376f
	if (!opts->credentials)
Packit Service 20376f
		return error_no_credentials();
Packit Service 20376f
Packit Service 20376f
	/* TODO: see if PROXYAUTH_AVAIL helps us here */
Packit Service 20376f
	git_cred_free(s->proxy_cred);
Packit Service 20376f
	s->proxy_cred = NULL;
Packit Service 20376f
	giterr_clear();
Packit Service 20376f
	error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload);
Packit Service 20376f
	if (error == GIT_PASSTHROUGH)
Packit Service 20376f
		return error_no_credentials();
Packit Service 20376f
	if (error < 0) {
Packit Service 20376f
		if (!giterr_last())
Packit Service 20376f
			giterr_set(GITERR_NET, "proxy authentication was aborted by the user");
Packit Service 20376f
		return error;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
Packit Service 20376f
		giterr_set(GITERR_NET, "credentials callback returned invalid credential type");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return apply_proxy_creds(s);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int curls_connect(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
	git_activesocket_t sockextr;
Packit Service 20376f
	long connect_last = 0;
Packit Service 20376f
	int failed_cert = 0, error;
Packit Service 20376f
	bool retry_connect;
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
Packit Service 20376f
	/* Apply any credentials we've already established */
Packit Service 20376f
	error = apply_proxy_creds(s);
Packit Service 20376f
	if (error < 0 && error != GIT_ENOTFOUND)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	do {
Packit Service 20376f
		retry_connect = 0;
Packit Service 20376f
		res = curl_easy_perform(s->handle);
Packit Service 20376f
Packit Service 20376f
		curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last);
Packit Service 20376f
Packit Service 20376f
		/* HTTP 407 Proxy Authentication Required */
Packit Service 20376f
		if (connect_last == 407) {
Packit Service 20376f
			if ((error = ask_and_apply_proxy_creds(s)) < 0)
Packit Service 20376f
				return error;
Packit Service 20376f
Packit Service 20376f
			retry_connect = true;
Packit Service 20376f
		}
Packit Service 20376f
	} while (retry_connect);
Packit Service 20376f
Packit Service 20376f
	if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
	if (res == CURLE_PEER_FAILED_VERIFICATION)
Packit Service 20376f
		failed_cert = 1;
Packit Service 20376f
Packit Service 20376f
	if ((res = curl_easy_getinfo(s->handle, CURLINFO_ACTIVESOCKET, &sockextr)) != CURLE_OK) {
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (sockextr == GIT_CURL_BADSOCKET) {
Packit Service 20376f
		giterr_set(GITERR_NET, "curl socket is no longer valid");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	s->socket = sockextr;
Packit Service 20376f
Packit Service 20376f
	if (s->parent.encrypted && failed_cert)
Packit Service 20376f
		return GIT_ECERTIFICATE;
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int curls_certificate(git_cert **out, git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
	struct curl_slist *slist;
Packit Service 20376f
	struct curl_certinfo *certinfo;
Packit Service 20376f
	git_vector strings = GIT_VECTOR_INIT;
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	/* No information is available, can happen with SecureTransport */
Packit Service 20376f
	if (certinfo->num_of_certs == 0) {
Packit Service 20376f
		s->cert_info.parent.cert_type = GIT_CERT_NONE;
Packit Service 20376f
		s->cert_info.data             = NULL;
Packit Service 20376f
		s->cert_info.len              = 0;
Packit Service 20376f
		return 0;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((error = git_vector_init(&strings, 8, NULL)) < 0)
Packit Service 20376f
		return error;
Packit Service 20376f
Packit Service 20376f
	for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
Packit Service 20376f
		char *str = git__strdup(slist->data);
Packit Service 20376f
		GITERR_CHECK_ALLOC(str);
Packit Service 20376f
		git_vector_insert(&strings, str);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	/* Copy the contents of the vector into a strarray so we can expose them */
Packit Service 20376f
	s->cert_info_strings.strings = (char **) strings.contents;
Packit Service 20376f
	s->cert_info_strings.count   = strings.length;
Packit Service 20376f
Packit Service 20376f
	s->cert_info.parent.cert_type = GIT_CERT_STRARRAY;
Packit Service 20376f
	s->cert_info.data             = &s->cert_info_strings;
Packit Service 20376f
	s->cert_info.len              = strings.length;
Packit Service 20376f
Packit Service 20376f
	*out = &s->cert_info.parent;
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	git_proxy_options_clear(&s->proxy);
Packit Service 20376f
	if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0)
Packit Service 20376f
		return error;
Packit Service 20376f
Packit Service 20376f
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int wait_for(curl_socket_t fd, bool reading)
Packit Service 20376f
{
Packit Service 20376f
	int ret;
Packit Service 20376f
	fd_set infd, outfd, errfd;
Packit Service 20376f
Packit Service 20376f
	FD_ZERO(&infd;;
Packit Service 20376f
	FD_ZERO(&outfd);
Packit Service 20376f
	FD_ZERO(&errfd);
Packit Service 20376f
Packit Service 20376f
	assert(fd >= 0);
Packit Service 20376f
	FD_SET(fd, &errfd);
Packit Service 20376f
	if (reading)
Packit Service 20376f
		FD_SET(fd, &infd;;
Packit Service 20376f
	else
Packit Service 20376f
		FD_SET(fd, &outfd);
Packit Service 20376f
Packit Service 20376f
	if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) {
Packit Service 20376f
		giterr_set(GITERR_OS, "error in select");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	size_t off = 0, sent;
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	GIT_UNUSED(flags);
Packit Service 20376f
Packit Service 20376f
	do {
Packit Service 20376f
		if ((error = wait_for(s->socket, false)) < 0)
Packit Service 20376f
			return error;
Packit Service 20376f
Packit Service 20376f
		res = curl_easy_send(s->handle, data + off, len - off, &sent);
Packit Service 20376f
		if (res == CURLE_OK)
Packit Service 20376f
			off += sent;
Packit Service 20376f
	} while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len);
Packit Service 20376f
Packit Service 20376f
	if (res != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	return len;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static ssize_t curls_read(git_stream *stream, void *data, size_t len)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	size_t read;
Packit Service 20376f
	CURLcode res;
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	do {
Packit Service 20376f
		if ((error = wait_for(s->socket, true)) < 0)
Packit Service 20376f
			return error;
Packit Service 20376f
Packit Service 20376f
		res = curl_easy_recv(s->handle, data, len, &read;;
Packit Service 20376f
	} while (res == CURLE_AGAIN);
Packit Service 20376f
Packit Service 20376f
	if (res != CURLE_OK)
Packit Service 20376f
		return seterr_curl(s);
Packit Service 20376f
Packit Service 20376f
	return read;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int curls_close(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	if (!s->handle)
Packit Service 20376f
		return 0;
Packit Service 20376f
Packit Service 20376f
	curl_easy_cleanup(s->handle);
Packit Service 20376f
	s->handle = NULL;
Packit Service 20376f
	s->socket = 0;
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void curls_free(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	curl_stream *s = (curl_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	curls_close(stream);
Packit Service 20376f
	git_strarray_free(&s->cert_info_strings);
Packit Service 20376f
	git_proxy_options_clear(&s->proxy);
Packit Service 20376f
	git_cred_free(s->proxy_cred);
Packit Service 20376f
	git__free(s);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
Packit Service 20376f
{
Packit Service 20376f
	curl_stream *st;
Packit Service 20376f
	CURL *handle;
Packit Service 20376f
	int iport = 0, error;
Packit Service 20376f
Packit Service 20376f
	st = git__calloc(1, sizeof(curl_stream));
Packit Service 20376f
	GITERR_CHECK_ALLOC(st);
Packit Service 20376f
Packit Service 20376f
	handle = curl_easy_init();
Packit Service 20376f
	if (handle == NULL) {
Packit Service 20376f
		giterr_set(GITERR_NET, "failed to create curl handle");
Packit Service 20376f
		git__free(st);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((error = git__strntol32(&iport, port, strlen(port), NULL, 10)) < 0) {
Packit Service 20376f
		git__free(st);
Packit Service 20376f
		return error;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_URL, host);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_PORT, iport);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_CERTINFO, 1);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1);
Packit Service 20376f
	curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
Packit Service 20376f
Packit Service 20376f
	/* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */
Packit Service 20376f
Packit Service 20376f
	st->parent.version = GIT_STREAM_VERSION;
Packit Service 20376f
	st->parent.encrypted = 0; /* we don't encrypt ourselves */
Packit Service 20376f
	st->parent.proxy_support = 1;
Packit Service 20376f
	st->parent.connect = curls_connect;
Packit Service 20376f
	st->parent.certificate = curls_certificate;
Packit Service 20376f
	st->parent.set_proxy = curls_set_proxy;
Packit Service 20376f
	st->parent.read = curls_read;
Packit Service 20376f
	st->parent.write = curls_write;
Packit Service 20376f
	st->parent.close = curls_close;
Packit Service 20376f
	st->parent.free = curls_free;
Packit Service 20376f
	st->handle = handle;
Packit Service 20376f
Packit Service 20376f
	*out = (git_stream *) st;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
#else
Packit Service 20376f
Packit Service 20376f
#include "stream.h"
Packit Service 20376f
Packit Service 20376f
int git_curl_stream_global_init(void)
Packit Service 20376f
{
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
Packit Service 20376f
{
Packit Service 20376f
	GIT_UNUSED(out);
Packit Service 20376f
	GIT_UNUSED(host);
Packit Service 20376f
	GIT_UNUSED(port);
Packit Service 20376f
Packit Service 20376f
	giterr_set(GITERR_NET, "curl is not supported in this version");
Packit Service 20376f
	return -1;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
Packit Service 20376f
#endif