netops.c 16.2 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6
 */
7
#ifndef _WIN32
8 9 10
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <sys/select.h>
11
#	include <sys/time.h>
12
#	include <netdb.h>
13
#	include <netinet/in.h>
14
#       include <arpa/inet.h>
15
#else
16
#	include <ws2tcpip.h>
17
#	ifdef _MSC_VER
18
#		pragma comment(lib, "ws2_32")
19
#	endif
20
#endif
21

22
#ifdef GIT_SSL
23
# include <openssl/ssl.h>
24
# include <openssl/err.h>
25
# include <openssl/x509v3.h>
26
#endif
27

28
#include <ctype.h>
29 30 31 32
#include "git2/errors.h"

#include "common.h"
#include "netops.h"
33
#include "posix.h"
34
#include "buffer.h"
35
#include "http_parser.h"
36 37 38 39

#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
40
	int error = WSAGetLastError();
41
	char * win32_error = git_win32_get_error_message(error);
42

43 44 45 46 47
	if (win32_error) {
		giterr_set(GITERR_NET, "%s: %s", str, win32_error);
		git__free(win32_error);
	} else {
		giterr_set(GITERR_NET, str);
48
	}
49 50 51 52 53 54 55
}
#else
static void net_set_error(const char *str)
{
	giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
}
#endif
56

57
#ifdef GIT_SSL
58 59 60
static int ssl_set_error(gitno_ssl *ssl, int error)
{
	int err;
61 62
	unsigned long e;

63
	err = SSL_get_error(ssl->ssl, error);
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 97 98

	assert(err != SSL_ERROR_WANT_READ);
	assert(err != SSL_ERROR_WANT_WRITE);

	switch (err) {
	case SSL_ERROR_WANT_CONNECT:
	case SSL_ERROR_WANT_ACCEPT:
		giterr_set(GITERR_NET, "SSL error: connection failure\n");
		break;
	case SSL_ERROR_WANT_X509_LOOKUP:
		giterr_set(GITERR_NET, "SSL error: x509 error\n");
		break;
	case SSL_ERROR_SYSCALL:
		e = ERR_get_error();
		if (e > 0) {
			giterr_set(GITERR_NET, "SSL error: %s",
					ERR_error_string(e, NULL));
			break;
		} else if (error < 0) {
			giterr_set(GITERR_OS, "SSL error: syscall failure");
			break;
		}
		giterr_set(GITERR_NET, "SSL error: received early EOF");
		break;
	case SSL_ERROR_SSL:
		e = ERR_get_error();
		giterr_set(GITERR_NET, "SSL error: %s",
				ERR_error_string(e, NULL));
		break;
	case SSL_ERROR_NONE:
	case SSL_ERROR_ZERO_RETURN:
	default:
		giterr_set(GITERR_NET, "SSL error: unknown error");
		break;
	}
99 100
	return -1;
}
101 102
#endif

103 104 105
int gitno_recv(gitno_buffer *buf)
{
	return buf->recv(buf);
106 107
}

108
#ifdef GIT_SSL
109
static int gitno__recv_ssl(gitno_buffer *buf)
110 111 112 113
{
	int ret;

	do {
114 115
		ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
	} while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
116

117 118 119 120
	if (ret < 0) {
		net_set_error("Error receiving socket data");
		return -1;
	}
121

122
	buf->offset += ret;
123
	return ret;
124
}
125
#endif
126

127
static int gitno__recv(gitno_buffer *buf)
128 129 130
{
	int ret;

131
	ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
132
	if (ret < 0) {
133
		net_set_error("Error receiving socket data");
134 135
		return -1;
	}
136 137 138 139 140

	buf->offset += ret;
	return ret;
}

141
void gitno_buffer_setup_callback(
142
	gitno_socket *socket,
143 144 145 146
	gitno_buffer *buf,
	char *data,
	size_t len,
	int (*recv)(gitno_buffer *buf), void *cb_data)
147 148 149 150 151
{
	memset(data, 0x0, len);
	buf->data = data;
	buf->len = len;
	buf->offset = 0;
152
	buf->socket = socket;
153 154 155 156
	buf->recv = recv;
	buf->cb_data = cb_data;
}

157
void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
158 159
{
#ifdef GIT_SSL
160 161 162 163
	if (socket->ssl.ctx) {
		gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
		return;
	}
164
#endif
165 166

	gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
167 168
}

169
/* Consume up to ptr and move the rest of the buffer to the beginning */
170
void gitno_consume(gitno_buffer *buf, const char *ptr)
171
{
172
	size_t consumed;
173

174
	assert(ptr - buf->data >= 0);
175 176
	assert(ptr - buf->data <= (int) buf->len);

177
	consumed = ptr - buf->data;
178

179 180 181
	memmove(buf->data, ptr, buf->offset - consumed);
	memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
	buf->offset -= consumed;
182 183 184
}

/* Consume const bytes and move the rest of the buffer to the beginning */
185
void gitno_consume_n(gitno_buffer *buf, size_t cons)
186 187 188 189 190 191
{
	memmove(buf->data, buf->data + cons, buf->len - buf->offset);
	memset(buf->data + cons, 0x0, buf->len - buf->offset);
	buf->offset -= cons;
}

192
#ifdef GIT_SSL
193

194 195 196
static int gitno_ssl_teardown(gitno_ssl *ssl)
{
	int ret;
197

198
	ret = SSL_shutdown(ssl->ssl);
199
	if (ret < 0)
200 201 202
		ret = ssl_set_error(ssl, ret);
	else
		ret = 0;
203

204 205
	SSL_free(ssl->ssl);
	SSL_CTX_free(ssl->ctx);
206
	return ret;
207 208
}

209
/* Match host names according to RFC 2818 rules */
210 211 212
static int match_host(const char *pattern, const char *host)
{
	for (;;) {
213
		char c = tolower(*pattern++);
214 215 216 217 218 219 220 221 222 223

		if (c == '\0')
			return *host ? -1 : 0;

		if (c == '*') {
			c = *pattern;
			/* '*' at the end matches everything left */
			if (c == '\0')
				return 0;

224 225 226 227 228 229 230 231 232 233 234 235 236
	/*
	 * We've found a pattern, so move towards the next matching
	 * char. The '.' is handled specially because wildcards aren't
	 * allowed to cross subdomains.
	 */

			while(*host) {
				char h = tolower(*host);
				if (c == h)
					return match_host(pattern, host++);
				if (h == '.')
					return match_host(pattern, host);
				host++;
237
			}
238
			return -1;
239 240
		}

241
		if (c != tolower(*host++))
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
			return -1;
	}

	return -1;
}

static int check_host_name(const char *name, const char *host)
{
	if (!strcasecmp(name, host))
		return 0;

	if (match_host(name, host) < 0)
		return -1;

	return 0;
}

259
static int verify_server_cert(gitno_ssl *ssl, const char *host)
260 261 262
{
	X509 *cert;
	X509_NAME *peer_name;
263 264
	ASN1_STRING *str;
	unsigned char *peer_cn = NULL;
265
	int matched = -1, type = GEN_DNS;
266
	GENERAL_NAMES *alts;
267 268 269
	struct in6_addr addr6;
	struct in_addr addr4;
	void *addr;
270 271
	int i = -1,j;

272
	if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
273 274 275
		giterr_set(GITERR_SSL, "The SSL certificate is invalid");
		return -1;
	}
276 277

	/* Try to parse the host as an IP address to see if it is */
278
	if (p_inet_pton(AF_INET, host, &addr4)) {
279 280 281
		type = GEN_IPADD;
		addr = &addr4;
	} else {
282
		if(p_inet_pton(AF_INET6, host, &addr6)) {
283 284 285 286 287
			type = GEN_IPADD;
			addr = &addr6;
		}
	}

288

289
	cert = SSL_get_peer_certificate(ssl->ssl);
290 291 292 293

	/* Check the alternative names */
	alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
	if (alts) {
294
		int num;
295 296 297 298 299 300 301

		num = sk_GENERAL_NAME_num(alts);
		for (i = 0; i < num && matched != 1; i++) {
			const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
			const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
			size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);

302 303
			/* Skip any names of a type we're not looking for */
			if (gn->type != type)
304 305
				continue;

306 307
			if (type == GEN_DNS) {
				/* If it contains embedded NULs, don't even try */
308
				if (memchr(name, '\0', namelen))
309 310 311 312 313 314 315 316 317 318
					continue;

				if (check_host_name(name, host) < 0)
					matched = 0;
				else
					matched = 1;
			} else if (type == GEN_IPADD) {
				/* Here name isn't so much a name but a binary representation of the IP */
				matched = !!memcmp(name, addr, namelen);
			}
319 320 321 322
		}
	}
	GENERAL_NAMES_free(alts);

323
	if (matched == 0)
324
		goto cert_fail;
325

326 327 328 329 330
	if (matched == 1)
		return 0;

	/* If no alternative names are available, check the common name */
	peer_name = X509_get_subject_name(cert);
331 332 333 334 335 336 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
	if (peer_name == NULL)
		goto on_error;

	if (peer_name) {
		/* Get the index of the last CN entry */
		while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
			i = j;
	}

	if (i < 0)
		goto on_error;

	str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
	if (str == NULL)
		goto on_error;

	/* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
	if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
		int size = ASN1_STRING_length(str);

		if (size > 0) {
			peer_cn = OPENSSL_malloc(size + 1);
			GITERR_CHECK_ALLOC(peer_cn);
			memcpy(peer_cn, ASN1_STRING_data(str), size);
			peer_cn[size] = '\0';
		}
	} else {
		int size = ASN1_STRING_to_UTF8(&peer_cn, str);
		GITERR_CHECK_ALLOC(peer_cn);
		if (memchr(peer_cn, '\0', size))
			goto cert_fail;
362 363
	}

364 365 366 367 368
	if (check_host_name((char *)peer_cn, host) < 0)
		goto cert_fail;

	OPENSSL_free(peer_cn);

369
	return 0;
370 371 372

on_error:
	OPENSSL_free(peer_cn);
373
	return ssl_set_error(ssl, 0);
374 375 376 377 378

cert_fail:
	OPENSSL_free(peer_cn);
	giterr_set(GITERR_SSL, "Certificate host name check failed");
	return -1;
379 380
}

381
static int ssl_setup(gitno_socket *socket, const char *host, int flags)
382
{
383 384 385 386
	int ret;

	SSL_library_init();
	SSL_load_error_strings();
387 388 389
	socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
	if (socket->ssl.ctx == NULL)
		return ssl_set_error(&socket->ssl, 0);
390

391 392 393 394
	SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
	SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
	if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
		return ssl_set_error(&socket->ssl, 0);
395

396 397 398
	socket->ssl.ssl = SSL_new(socket->ssl.ctx);
	if (socket->ssl.ssl == NULL)
		return ssl_set_error(&socket->ssl, 0);
399

400 401
	if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
		return ssl_set_error(&socket->ssl, ret);
402

403 404
	if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
		return ssl_set_error(&socket->ssl, ret);
405

406 407
	if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
		return 0;
408

409
	return verify_server_cert(&socket->ssl, host);
410
}
411 412 413
#endif

static int gitno__close(GIT_SOCKET s)
414
{
415 416 417 418 419 420 421 422 423
#ifdef GIT_WIN32
	if (SOCKET_ERROR == closesocket(s))
		return -1;

	if (0 != WSACleanup()) {
		giterr_set(GITERR_OS, "Winsock cleanup failed");
		return -1;
	}

424
	return 0;
425 426
#else
	return close(s);
427
#endif
428
}
429

430
int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
431
{
432
	struct addrinfo *info = NULL, *p;
433
	struct addrinfo hints;
434
	GIT_SOCKET s = INVALID_SOCKET;
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
	int ret;

#ifdef GIT_WIN32
	/* on win32, the WSA context needs to be initialized
	 * before any socket calls can be performed */
	WSADATA wsd;

	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
		giterr_set(GITERR_OS, "Winsock init failed");
		return -1;
	}

	if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
		WSACleanup();
		giterr_set(GITERR_OS, "Winsock init failed");
		return -1;
	}
#endif

	/* Zero the socket structure provided */
	memset(s_out, 0x0, sizeof(gitno_socket));
456 457 458

	memset(&hints, 0x0, sizeof(struct addrinfo));
	hints.ai_socktype = SOCK_STREAM;
459
	hints.ai_family = AF_UNSPEC;
460

Vicent Marti committed
461 462 463
	if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
		giterr_set(GITERR_NET,
			"Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
464
		return -1;
465 466 467 468
	}

	for (p = info; p != NULL; p = p->ai_next) {
		s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
469

470
		if (s == INVALID_SOCKET) {
471 472
			net_set_error("error creating socket");
			break;
473 474
		}

475 476
		if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
			break;
477

478
		/* If we can't connect, try the next one */
479
		gitno__close(s);
480
		s = INVALID_SOCKET;
481 482 483
	}

	/* Oops, we couldn't connect to any address */
484
	if (s == INVALID_SOCKET && p == NULL) {
485
		giterr_set(GITERR_OS, "Failed to connect to %s", host);
Philip Kelley committed
486
		p_freeaddrinfo(info);
487 488
		return -1;
	}
489

490
	s_out->socket = s;
Vicent Marti committed
491
	p_freeaddrinfo(info);
492

493 494
#ifdef GIT_SSL
	if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
495
		return -1;
496 497 498 499 500 501 502
#else
	/* SSL is not supported */
	if (flags & GITNO_CONNECT_SSL) {
		giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
		return -1;
	}
#endif
503

504
	return 0;
505
}
506

507
#ifdef GIT_SSL
508
static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
509
{
510 511
	int ret;
	size_t off = 0;
512

513 514
	GIT_UNUSED(flags);

515
	while (off < len) {
516
		ret = SSL_write(ssl->ssl, msg + off, len - off);
517
		if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
518 519 520
			return ssl_set_error(ssl, ret);

		off += ret;
521
	}	
522

523 524
	return off;
}
525 526
#endif

527
int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
528 529 530 531
{
	int ret;
	size_t off = 0;

532
#ifdef GIT_SSL
533 534
	if (socket->ssl.ctx)
		return gitno_send_ssl(&socket->ssl, msg, len, flags);
535 536 537 538
#endif

	while (off < len) {
		errno = 0;
539
		ret = p_send(socket->socket, msg + off, len - off, flags);
540
		if (ret < 0) {
541
			net_set_error("Error sending data");
542 543
			return -1;
		}
544 545 546 547

		off += ret;
	}

548
	return (int)off;
549
}
550

551
int gitno_close(gitno_socket *s)
552
{
553 554 555 556
#ifdef GIT_SSL
	if (s->ssl.ctx &&
		gitno_ssl_teardown(&s->ssl) < 0)
		return -1;
557 558
#endif

559 560 561
	return gitno__close(s->socket);
}

562 563 564 565 566 567 568 569 570
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
{
	fd_set fds;
	struct timeval tv;

	tv.tv_sec = sec;
	tv.tv_usec = usec;

	FD_ZERO(&fds);
571
	FD_SET(buf->socket->socket, &fds);
572 573

	/* The select(2) interface is silly */
574
	return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
575
}
576

577 578 579 580 581 582
static const char *prefix_http = "http://";
static const char *prefix_https = "https://";

int gitno_connection_data_from_url(
		gitno_connection_data *data,
		const char *url,
583
		const char *service_suffix)
584
{
585
	int error = -1;
586
	const char *default_port = NULL, *path_search_start = NULL;
587
	char *original_host = NULL;
588 589 590 591

	/* service_suffix is optional */
	assert(data && url);

592
	/* Save these for comparison later */
593 594
	original_host = data->host;
	data->host = NULL;
595 596
	gitno_connection_data_free_ptrs(data);

597
	if (!git__prefixcmp(url, prefix_http)) {
598
		path_search_start = url + strlen(prefix_http);
599 600 601 602
		default_port = "80";

		if (data->use_ssl) {
			giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed");
603
			goto cleanup;
604
		}
605 606
	} else if (!git__prefixcmp(url, prefix_https)) {
		path_search_start = url + strlen(prefix_https);
607 608
		default_port = "443";
		data->use_ssl = true;
609
	} else if (url[0] == '/')
610 611
		default_port = data->use_ssl ? "443" : "80";

612 613
	if (!default_port) {
		giterr_set(GITERR_NET, "Unrecognized URL prefix");
614
		goto cleanup;
615 616 617
	}

	error = gitno_extract_url_parts(
618
		&data->host, &data->port, &data->path, &data->user, &data->pass,
619 620
		url, default_port);

621 622
	if (url[0] == '/') {
		/* Relative redirect; reuse original host name and port */
623
		path_search_start = url;
624 625 626 627 628
		git__free(data->host);
		data->host = original_host;
		original_host = NULL;
	}

629
	if (!error) {
630
		const char *path = strchr(path_search_start, '/');
631 632 633 634
		size_t pathlen = strlen(path);
		size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;

		if (suffixlen &&
Carlos Martín Nieto committed
635 636
		    !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
			git__free(data->path);
637
			data->path = git__strndup(path, pathlen - suffixlen);
Carlos Martín Nieto committed
638 639
		} else {
			git__free(data->path);
640
			data->path = git__strdup(path);
Carlos Martín Nieto committed
641
		}
642 643 644 645 646 647 648 649

		/* Check for errors in the resulting data */
		if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
			giterr_set(GITERR_NET, "Cross host redirect not allowed");
			error = -1;
		}
	}

650 651
cleanup:
	if (original_host) git__free(original_host);
652 653 654 655 656 657 658 659 660 661 662 663
	return error;
}

void gitno_connection_data_free_ptrs(gitno_connection_data *d)
{
	git__free(d->host); d->host = NULL;
	git__free(d->port); d->port = NULL;
	git__free(d->path); d->path = NULL;
	git__free(d->user); d->user = NULL;
	git__free(d->pass); d->pass = NULL;
}

664
#define hex2c(c) ((c | 32) % 39 - 9)
665 666 667
static char* unescape(char *str)
{
	int x, y;
Russell Belfer committed
668
	int len = (int)strlen(str);
669

670
	for (x=y=0; str[y]; ++x, ++y) {
671
		if ((str[x] = str[y]) == '%') {
672 673 674 675
			if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) {
				str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]);
				y += 2;
			}
676 677 678 679 680 681
		}
	}
	str[x] = '\0';
	return str;
}

682 683 684
int gitno_extract_url_parts(
		char **host,
		char **port,
685
		char **path,
686 687 688 689
		char **username,
		char **password,
		const char *url,
		const char *default_port)
690
{
691 692
	struct http_parser_url u = {0};
	const char *_host, *_port, *_path, *_userinfo;
693

694 695 696
	if (http_parser_parse_url(url, strlen(url), false, &u)) {
		giterr_set(GITERR_NET, "Malformed URL '%s'", url);
		return GIT_EINVALIDSPEC;
697
	}
698

699 700 701 702 703
	_host = url+u.field_data[UF_HOST].off;
	_port = url+u.field_data[UF_PORT].off;
	_path = url+u.field_data[UF_PATH].off;
	_userinfo = url+u.field_data[UF_USERINFO].off;

Ben Straub committed
704
	if (u.field_set & (1 << UF_HOST)) {
705 706
		*host = git__substrdup(_host, u.field_data[UF_HOST].len);
		GITERR_CHECK_ALLOC(*host);
707
	}
708

Ben Straub committed
709
	if (u.field_set & (1 << UF_PORT))
710 711
		*port = git__substrdup(_port, u.field_data[UF_PORT].len);
	else
712 713
		*port = git__strdup(default_port);
	GITERR_CHECK_ALLOC(*port);
714

Ben Straub committed
715
	if (u.field_set & (1 << UF_PATH)) {
716 717 718 719
		*path = git__substrdup(_path, u.field_data[UF_PATH].len);
		GITERR_CHECK_ALLOC(*path);
	}

Ben Straub committed
720 721
	if (u.field_set & (1 << UF_USERINFO)) {
		const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
Ben Straub committed
722
		if (colon) {
723 724
			*username = unescape(git__substrdup(_userinfo, colon - _userinfo));
			*password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo)));
725 726 727 728 729
			GITERR_CHECK_ALLOC(*password);
		} else {
			*username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
		}
		GITERR_CHECK_ALLOC(*username);
730

731 732
	}

733
	return 0;
734
}