netops.c 13.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

Fraser Tweedale committed
22 23 24 25
#ifdef __FreeBSD__
#	include <netinet/in.h>
#endif

26
#ifdef GIT_SSL
27
# include <openssl/ssl.h>
28
# include <openssl/err.h>
29
# include <openssl/x509v3.h>
30
#endif
31

32
#include <ctype.h>
33 34 35 36
#include "git2/errors.h"

#include "common.h"
#include "netops.h"
37
#include "posix.h"
38 39 40 41 42 43 44 45 46 47 48
#include "buffer.h"

#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
	int size, error = WSAGetLastError();
	LPSTR err_str = NULL;

	size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			     0, error, 0, (LPSTR)&err_str, 0, 0);

49 50
	GIT_UNUSED(size);

Carlos Martín Nieto committed
51
	giterr_set(GITERR_NET, "%s: %s", str, err_str);
52 53 54 55 56 57 58 59
	LocalFree(err_str);
}
#else
static void net_set_error(const char *str)
{
	giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
}
#endif
60

61
#ifdef GIT_SSL
62 63 64
static int ssl_set_error(gitno_ssl *ssl, int error)
{
	int err;
65 66
	unsigned long e;

67
	err = SSL_get_error(ssl->ssl, error);
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 99 100 101 102

	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;
	}
103 104
	return -1;
}
105 106
#endif

107 108 109
int gitno_recv(gitno_buffer *buf)
{
	return buf->recv(buf);
110 111
}

112
#ifdef GIT_SSL
113
static int gitno__recv_ssl(gitno_buffer *buf)
114 115 116 117
{
	int ret;

	do {
118 119
		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);
120

121 122 123 124
	if (ret < 0) {
		net_set_error("Error receiving socket data");
		return -1;
	}
125

126
	buf->offset += ret;
127
	return ret;
128
}
129
#endif
130

131
static int gitno__recv(gitno_buffer *buf)
132 133 134
{
	int ret;

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

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

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

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

	gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
171 172
}

173
/* Consume up to ptr and move the rest of the buffer to the beginning */
174
void gitno_consume(gitno_buffer *buf, const char *ptr)
175
{
176
	size_t consumed;
177

178
	assert(ptr - buf->data >= 0);
179 180
	assert(ptr - buf->data <= (int) buf->len);

181
	consumed = ptr - buf->data;
182

183 184 185
	memmove(buf->data, ptr, buf->offset - consumed);
	memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
	buf->offset -= consumed;
186 187 188
}

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

196
#ifdef GIT_SSL
197

198 199 200
static int gitno_ssl_teardown(gitno_ssl *ssl)
{
	int ret;
201

202
	ret = SSL_shutdown(ssl->ssl);
203
	if (ret < 0)
204 205 206
		ret = ssl_set_error(ssl, ret);
	else
		ret = 0;
207

208 209
	SSL_free(ssl->ssl);
	SSL_CTX_free(ssl->ctx);
210
	return ret;
211 212
}

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

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

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

228 229 230 231 232 233 234 235 236 237 238 239 240
	/*
	 * 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++;
241
			}
242
			return -1;
243 244
		}

245
		if (c != tolower(*host++))
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
			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;
}

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

276
	if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
277 278 279
		giterr_set(GITERR_SSL, "The SSL certificate is invalid");
		return -1;
	}
280 281

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

292

293
	cert = SSL_get_peer_certificate(ssl->ssl);
294 295 296 297

	/* Check the alternative names */
	alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
	if (alts) {
298
		int num;
299 300 301 302 303 304 305

		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);

306 307
			/* Skip any names of a type we're not looking for */
			if (gn->type != type)
308 309
				continue;

310 311
			if (type == GEN_DNS) {
				/* If it contains embedded NULs, don't even try */
312
				if (memchr(name, '\0', namelen))
313 314 315 316 317 318 319 320 321 322
					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);
			}
323 324 325 326
		}
	}
	GENERAL_NAMES_free(alts);

327
	if (matched == 0)
328
		goto cert_fail;
329

330 331 332 333 334
	if (matched == 1)
		return 0;

	/* If no alternative names are available, check the common name */
	peer_name = X509_get_subject_name(cert);
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 362 363 364 365
	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;
366 367
	}

368 369 370 371 372
	if (check_host_name((char *)peer_cn, host) < 0)
		goto cert_fail;

	OPENSSL_free(peer_cn);

373
	return 0;
374 375 376

on_error:
	OPENSSL_free(peer_cn);
377
	return ssl_set_error(ssl, 0);
378 379 380 381 382

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

385
static int ssl_setup(gitno_socket *socket, const char *host, int flags)
386
{
387 388 389 390
	int ret;

	SSL_library_init();
	SSL_load_error_strings();
391 392 393
	socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
	if (socket->ssl.ctx == NULL)
		return ssl_set_error(&socket->ssl, 0);
394

395 396 397 398
	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);
399

400 401 402
	socket->ssl.ssl = SSL_new(socket->ssl.ctx);
	if (socket->ssl.ssl == NULL)
		return ssl_set_error(&socket->ssl, 0);
403

404 405
	if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
		return ssl_set_error(&socket->ssl, ret);
406

407 408
	if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
		return ssl_set_error(&socket->ssl, ret);
409

410 411
	if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
		return 0;
412

413
	return verify_server_cert(&socket->ssl, host);
414
}
415 416 417
#endif

static int gitno__close(GIT_SOCKET s)
418
{
419 420 421 422 423 424 425 426 427
#ifdef GIT_WIN32
	if (SOCKET_ERROR == closesocket(s))
		return -1;

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

428
	return 0;
429 430
#else
	return close(s);
431
#endif
432
}
433

434
int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
435
{
436
	struct addrinfo *info = NULL, *p;
437
	struct addrinfo hints;
438
	GIT_SOCKET s = INVALID_SOCKET;
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
	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));
460 461 462

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

Vicent Marti committed
465 466 467
	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));
468
		return -1;
469 470 471 472
	}

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

474
		if (s == INVALID_SOCKET) {
475 476
			net_set_error("error creating socket");
			break;
477 478
		}

479 480
		if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
			break;
481

482
		/* If we can't connect, try the next one */
483
		gitno__close(s);
484
		s = INVALID_SOCKET;
485 486 487
	}

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

494
	s_out->socket = s;
Vicent Marti committed
495
	p_freeaddrinfo(info);
496

497 498
#ifdef GIT_SSL
	if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
499
		return -1;
500 501 502 503 504 505 506
#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
507

508
	return 0;
509
}
510

511
#ifdef GIT_SSL
512
static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
513
{
514 515
	int ret;
	size_t off = 0;
516

517 518
	GIT_UNUSED(flags);

519
	while (off < len) {
520
		ret = SSL_write(ssl->ssl, msg + off, len - off);
521
		if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
522 523 524
			return ssl_set_error(ssl, ret);

		off += ret;
525
	}	
526

527 528
	return off;
}
529 530
#endif

531
int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
532 533 534 535
{
	int ret;
	size_t off = 0;

536
#ifdef GIT_SSL
537 538
	if (socket->ssl.ctx)
		return gitno_send_ssl(&socket->ssl, msg, len, flags);
539 540 541 542
#endif

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

		off += ret;
	}

552
	return (int)off;
553
}
554

555
int gitno_close(gitno_socket *s)
556
{
557 558 559 560
#ifdef GIT_SSL
	if (s->ssl.ctx &&
		gitno_ssl_teardown(&s->ssl) < 0)
		return -1;
561 562
#endif

563 564 565
	return gitno__close(s->socket);
}

566 567 568 569 570 571 572 573 574
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);
575
	FD_SET(buf->socket->socket, &fds);
576 577

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

581 582 583 584 585 586 587
int gitno_extract_url_parts(
		char **host,
		char **port,
		char **username,
		char **password,
		const char *url,
		const char *default_port)
588
{
589
	char *colon, *slash, *at, *end;
590
	const char *start;
591

592 593 594 595 596
	/*
	 *
	 * ==> [user[:pass]@]hostname.tld[:port]/resource
	 */

597 598
	colon = strchr(url, ':');
	slash = strchr(url, '/');
599
	at = strchr(url, '@');
600

601
	if (slash == NULL) {
602 603
		giterr_set(GITERR_NET, "Malformed URL: missing /");
		return -1;
604
	}
605

606 607 608 609 610 611 612 613 614 615 616 617 618
	start = url;
	if (at && at < slash) {
		start = at+1;
		*username = git__strndup(url, at - url);
	}

	if (colon && colon < at) {
		git__free(*username);
		*username = git__strndup(url, colon-url);
		*password = git__strndup(colon+1, at-colon-1);
		colon = strchr(at, ':');
	}

619 620 621 622 623
	if (colon == NULL) {
		*port = git__strdup(default_port);
	} else {
		*port = git__strndup(colon + 1, slash - colon - 1);
	}
624
	GITERR_CHECK_ALLOC(*port);
625

626
	end = colon == NULL ? slash : colon;
627

628
	*host = git__strndup(start, end - start);
629
	GITERR_CHECK_ALLOC(*host);
630

631
	return 0;
632
}