curl_stream.c 8.41 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#ifdef GIT_CURL

#include <curl/curl.h>

#include "stream.h"
#include "git2/transport.h"
#include "buffer.h"
15
#include "vector.h"
16
#include "proxy.h"
17

18 19 20 21 22 23 24 25 26 27
/* This is for backwards compatibility with curl<7.45.0. */
#ifndef CURLINFO_ACTIVESOCKET
# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET
# define GIT_CURL_BADSOCKET -1
# define git_activesocket_t long
#else
# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD
# define git_activesocket_t curl_socket_t
#endif

28 29 30 31 32 33
typedef struct {
	git_stream parent;
	CURL *handle;
	curl_socket_t socket;
	char curl_error[CURL_ERROR_SIZE + 1];
	git_cert_x509 cert_info;
34
	git_strarray cert_info_strings;
35
	git_proxy_options proxy;
36
	git_cred *proxy_cred;
37 38 39 40 41 42 43 44
} curl_stream;

static int seterr_curl(curl_stream *s)
{
	giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
	return -1;
}

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
GIT_INLINE(int) error_no_credentials(void)
{
	giterr_set(GITERR_NET, "proxy authentication required, but no callback provided");
	return GIT_EAUTH;
}

static int apply_proxy_creds(curl_stream *s)
{
	CURLcode res;
	git_cred_userpass_plaintext *userpass;

	if (!s->proxy_cred)
		return GIT_ENOTFOUND;

	userpass = (git_cred_userpass_plaintext *) s->proxy_cred;
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK)
		return seterr_curl(s);
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK)
		return seterr_curl(s);

	return 0;
}

static int ask_and_apply_proxy_creds(curl_stream *s)
{
	int error;
	git_proxy_options *opts = &s->proxy;

	if (!opts->credentials)
		return error_no_credentials();

	/* TODO: see if PROXYAUTH_AVAIL helps us here */
	git_cred_free(s->proxy_cred);
	s->proxy_cred = NULL;
	giterr_clear();
	error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload);
	if (error == GIT_PASSTHROUGH)
		return error_no_credentials();
	if (error < 0) {
		if (!giterr_last())
			giterr_set(GITERR_NET, "proxy authentication was aborted by the user");
		return error;
	}

	if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
		giterr_set(GITERR_NET, "credentials callback returned invalid credential type");
		return -1;
	}

	return apply_proxy_creds(s);
}

97 98 99
static int curls_connect(git_stream *stream)
{
	curl_stream *s = (curl_stream *) stream;
100 101
	git_activesocket_t sockextr;
	long connect_last = 0;
102 103
	int failed_cert = 0, error;
	bool retry_connect;
104
	CURLcode res;
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

	/* Apply any credentials we've already established */
	error = apply_proxy_creds(s);
	if (error < 0 && error != GIT_ENOTFOUND)
		return seterr_curl(s);

	do {
		retry_connect = 0;
		res = curl_easy_perform(s->handle);

		curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last);

		/* HTTP 407 Proxy Authentication Required */
		if (connect_last == 407) {
			if ((error = ask_and_apply_proxy_creds(s)) < 0)
				return error;

			retry_connect = true;
		}
	} while (retry_connect);
125 126 127 128 129 130

	if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION)
		return seterr_curl(s);
	if (res == CURLE_PEER_FAILED_VERIFICATION)
		failed_cert = 1;

131
	if ((res = curl_easy_getinfo(s->handle, CURLINFO_ACTIVESOCKET, &sockextr)) != CURLE_OK) {
132
		return seterr_curl(s);
133
	}
134

135
	if (sockextr == GIT_CURL_BADSOCKET) {
136 137 138 139
		giterr_set(GITERR_NET, "curl socket is no longer valid");
		return -1;
	}

140 141 142 143 144 145 146 147 148 149
	s->socket = sockextr;

	if (s->parent.encrypted && failed_cert)
		return GIT_ECERTIFICATE;

	return 0;
}

static int curls_certificate(git_cert **out, git_stream *stream)
{
150 151 152 153 154
	int error;
	CURLcode res;
	struct curl_slist *slist;
	struct curl_certinfo *certinfo;
	git_vector strings = GIT_VECTOR_INIT;
155 156
	curl_stream *s = (curl_stream *) stream;

157 158 159 160 161
	if ((res = curl_easy_getinfo(s->handle, CURLINFO_CERTINFO, &certinfo)) != CURLE_OK)
		return seterr_curl(s);

	/* No information is available, can happen with SecureTransport */
	if (certinfo->num_of_certs == 0) {
162 163 164
		s->cert_info.parent.cert_type = GIT_CERT_NONE;
		s->cert_info.data             = NULL;
		s->cert_info.len              = 0;
165 166 167 168 169 170 171 172 173
		return 0;
	}

	if ((error = git_vector_init(&strings, 8, NULL)) < 0)
		return error;

	for (slist = certinfo->certinfo[0]; slist; slist = slist->next) {
		char *str = git__strdup(slist->data);
		GITERR_CHECK_ALLOC(str);
174
		git_vector_insert(&strings, str);
175 176 177 178 179 180
	}

	/* Copy the contents of the vector into a strarray so we can expose them */
	s->cert_info_strings.strings = (char **) strings.contents;
	s->cert_info_strings.count   = strings.length;

181 182 183
	s->cert_info.parent.cert_type = GIT_CERT_STRARRAY;
	s->cert_info.data             = &s->cert_info_strings;
	s->cert_info.len              = strings.length;
184

185
	*out = &s->cert_info.parent;
186 187 188 189

	return 0;
}

190
static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
191
{
192
	int error;
193 194 195
	CURLcode res;
	curl_stream *s = (curl_stream *) stream;

196 197 198 199
	if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0)
		return error;

	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK)
200 201
		return seterr_curl(s);

202 203 204
	if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK)
		return seterr_curl(s);

205 206 207
	return 0;
}

208 209 210 211 212 213 214 215 216
static int wait_for(curl_socket_t fd, bool reading)
{
	int ret;
	fd_set infd, outfd, errfd;

	FD_ZERO(&infd);
	FD_ZERO(&outfd);
	FD_ZERO(&errfd);

217
	assert(fd >= 0);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
	FD_SET(fd, &errfd);
	if (reading)
		FD_SET(fd, &infd);
	else
		FD_SET(fd, &outfd);

	if ((ret = select(fd + 1, &infd, &outfd, &errfd, NULL)) < 0) {
		giterr_set(GITERR_OS, "error in select");
		return -1;
	}

	return 0;
}

static ssize_t curls_write(git_stream *stream, const char *data, size_t len, int flags)
{
	int error;
	size_t off = 0, sent;
	CURLcode res;
	curl_stream *s = (curl_stream *) stream;

	GIT_UNUSED(flags);

	do {
		if ((error = wait_for(s->socket, false)) < 0)
			return error;

		res = curl_easy_send(s->handle, data + off, len - off, &sent);
		if (res == CURLE_OK)
			off += sent;
	} while ((res == CURLE_OK || res == CURLE_AGAIN) && off < len);

	if (res != CURLE_OK)
		return seterr_curl(s);

	return len;
}

static ssize_t curls_read(git_stream *stream, void *data, size_t len)
{
	int error;
	size_t read;
	CURLcode res;
	curl_stream *s = (curl_stream *) stream;

	do {
		if ((error = wait_for(s->socket, true)) < 0)
			return error;

		res = curl_easy_recv(s->handle, data, len, &read);
	} while (res == CURLE_AGAIN);

	if (res != CURLE_OK)
		return seterr_curl(s);

	return read;
}

static int curls_close(git_stream *stream)
{
	curl_stream *s = (curl_stream *) stream;

	if (!s->handle)
		return 0;

	curl_easy_cleanup(s->handle);
	s->handle = NULL;
	s->socket = 0;

	return 0;
}

static void curls_free(git_stream *stream)
{
	curl_stream *s = (curl_stream *) stream;

	curls_close(stream);
295
	git_strarray_free(&s->cert_info_strings);
296 297 298
	git__free(s);
}

299
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
300 301 302 303 304 305 306 307 308 309 310
{
	curl_stream *st;
	CURL *handle;
	int iport = 0, error;

	st = git__calloc(1, sizeof(curl_stream));
	GITERR_CHECK_ALLOC(st);

	handle = curl_easy_init();
	if (handle == NULL) {
		giterr_set(GITERR_NET, "failed to create curl handle");
311
		git__free(st);
312 313 314
		return -1;
	}

315 316
	if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) {
		git__free(st);
317
		return error;
318
	}
319

320
	curl_easy_setopt(handle, CURLOPT_URL, host);
321 322 323 324 325
	curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error);
	curl_easy_setopt(handle, CURLOPT_PORT, iport);
	curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 1);
	curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1);
	curl_easy_setopt(handle, CURLOPT_CERTINFO, 1);
326
	curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1);
327
	curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
328

329 330 331
	/* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */

	st->parent.version = GIT_STREAM_VERSION;
332
	st->parent.encrypted = 0; /* we don't encrypt ourselves */
333
	st->parent.proxy_support = 1;
334 335
	st->parent.connect = curls_connect;
	st->parent.certificate = curls_certificate;
336
	st->parent.set_proxy = curls_set_proxy;
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
	st->parent.read = curls_read;
	st->parent.write = curls_write;
	st->parent.close = curls_close;
	st->parent.free = curls_free;
	st->handle = handle;

	*out = (git_stream *) st;
	return 0;
}

#else

#include "stream.h"

int git_curl_stream_new(git_stream **out, const char *host, const char *port)
{
	GIT_UNUSED(out);
	GIT_UNUSED(host);
	GIT_UNUSED(port);

	giterr_set(GITERR_NET, "curl is not supported in this version");
	return -1;
}


#endif