]> git.proxmox.com Git - libgit2.git/blame - src/netops.c
Merge pull request #850 from libgit2/attr-export
[libgit2.git] / src / netops.c
CommitLineData
1b4f8140 1/*
5e0de328 2 * Copyright (C) 2009-2012 the libgit2 contributors
1b4f8140 3 *
bb742ede
VM
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
1b4f8140 6 */
39cdf272 7#ifndef _WIN32
6e34111e
VM
8# include <sys/types.h>
9# include <sys/socket.h>
10# include <sys/select.h>
31ffc141 11# include <sys/time.h>
6e34111e 12# include <netdb.h>
66798ad0 13# include <arpa/inet.h>
4e95ef02 14#else
c1318f71 15# include <ws2tcpip.h>
6e34111e 16# ifdef _MSC_VER
c1318f71 17# pragma comment(lib, "ws2_32.lib")
6e34111e 18# endif
4e95ef02 19#endif
1b4f8140 20
d3e1367f 21#ifdef GIT_SSL
a6f24a5b 22# include <openssl/ssl.h>
dbb36e1b 23# include <openssl/x509v3.h>
66024c7c 24#endif
6e34111e 25
dbb36e1b 26#include <ctype.h>
1b4f8140
CMN
27#include "git2/errors.h"
28
29#include "common.h"
30#include "netops.h"
34bfb4b0 31#include "posix.h"
bd6585a7 32#include "buffer.h"
66024c7c 33#include "transport.h"
bd6585a7
CMN
34
35#ifdef GIT_WIN32
36static void net_set_error(const char *str)
37{
38 int size, error = WSAGetLastError();
39 LPSTR err_str = NULL;
40
41 size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
42 0, error, 0, (LPSTR)&err_str, 0, 0);
43
65ca81a6 44 giterr_set(GITERR_NET, "%s: %s", str, err_str);
bd6585a7
CMN
45 LocalFree(err_str);
46}
47#else
48static void net_set_error(const char *str)
49{
50 giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
51}
52#endif
1b4f8140 53
d3e1367f 54#ifdef GIT_SSL
a6f24a5b
CMN
55static int ssl_set_error(gitno_ssl *ssl, int error)
56{
57 int err;
58 err = SSL_get_error(ssl->ssl, error);
59 giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL));
60 return -1;
61}
66024c7c
CMN
62#endif
63
b49c8f71
CMN
64int gitno_recv(gitno_buffer *buf)
65{
66 return buf->recv(buf);
66024c7c
CMN
67}
68
d3e1367f 69#ifdef GIT_SSL
8861d32f 70static int gitno__recv_ssl(gitno_buffer *buf)
a6f24a5b
CMN
71{
72 int ret;
73
74 do {
8861d32f
CMN
75 ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset);
76 } while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ);
a6f24a5b 77
8861d32f
CMN
78 if (ret < 0) {
79 net_set_error("Error receiving socket data");
80 return -1;
81 }
a6f24a5b 82
8861d32f 83 buf->offset += ret;
a6f24a5b 84 return ret;
ea7a5452 85}
a6f24a5b 86#endif
ea7a5452 87
b49c8f71 88int gitno__recv(gitno_buffer *buf)
ea7a5452
CMN
89{
90 int ret;
91
44ef8b1b 92 ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
56b7df10 93 if (ret < 0) {
44ef8b1b 94 net_set_error("Error receiving socket data");
56b7df10
CMN
95 return -1;
96 }
ea7a5452
CMN
97
98 buf->offset += ret;
ea7a5452
CMN
99 return ret;
100}
101
8861d32f
CMN
102void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, unsigned int len, int (*recv)(gitno_buffer *buf), void *cb_data)
103{
104 memset(buf, 0x0, sizeof(gitno_buffer));
105 memset(data, 0x0, len);
106 buf->data = data;
107 buf->len = len;
108 buf->offset = 0;
109 buf->fd = t->socket;
110 buf->recv = recv;
111 buf->cb_data = cb_data;
112}
113
114void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len)
115{
116#ifdef GIT_SSL
0048372a 117 if (t->use_ssl) {
8861d32f
CMN
118 gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL);
119 buf->ssl = &t->ssl;
120 } else
121#endif
122 gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL);
123}
124
ea7a5452 125/* Consume up to ptr and move the rest of the buffer to the beginning */
c7c787ce 126void gitno_consume(gitno_buffer *buf, const char *ptr)
ea7a5452 127{
0bd594b6 128 size_t consumed;
ea7a5452 129
0bd594b6 130 assert(ptr - buf->data >= 0);
ea7a5452
CMN
131 assert(ptr - buf->data <= (int) buf->len);
132
c7c787ce 133 consumed = ptr - buf->data;
ea7a5452 134
c7c787ce
CMN
135 memmove(buf->data, ptr, buf->offset - consumed);
136 memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
137 buf->offset -= consumed;
ea7a5452
CMN
138}
139
140/* Consume const bytes and move the rest of the buffer to the beginning */
0bd594b6 141void gitno_consume_n(gitno_buffer *buf, size_t cons)
ea7a5452
CMN
142{
143 memmove(buf->data, buf->data + cons, buf->len - buf->offset);
144 memset(buf->data + cons, 0x0, buf->len - buf->offset);
145 buf->offset -= cons;
146}
147
89460f3f
CMN
148int gitno_ssl_teardown(git_transport *t)
149{
6f944ab1 150#ifdef GIT_SSL
151 int ret;
152#endif
89460f3f 153
0048372a 154 if (!t->use_ssl)
89460f3f
CMN
155 return 0;
156
d3e1367f 157#ifdef GIT_SSL
89460f3f
CMN
158
159 do {
160 ret = SSL_shutdown(t->ssl.ssl);
161 } while (ret == 0);
162 if (ret < 0)
163 return ssl_set_error(&t->ssl, ret);
164
165 SSL_free(t->ssl.ssl);
166 SSL_CTX_free(t->ssl.ctx);
167#endif
168 return 0;
169}
170
171
d3e1367f 172#ifdef GIT_SSL
16768191 173/* Match host names according to RFC 2818 rules */
dbb36e1b
CMN
174static int match_host(const char *pattern, const char *host)
175{
176 for (;;) {
16768191 177 char c = tolower(*pattern++);
dbb36e1b
CMN
178
179 if (c == '\0')
180 return *host ? -1 : 0;
181
182 if (c == '*') {
183 c = *pattern;
184 /* '*' at the end matches everything left */
185 if (c == '\0')
186 return 0;
187
16768191
CMN
188 /*
189 * We've found a pattern, so move towards the next matching
190 * char. The '.' is handled specially because wildcards aren't
191 * allowed to cross subdomains.
192 */
193
194 while(*host) {
195 char h = tolower(*host);
196 if (c == h)
197 return match_host(pattern, host++);
198 if (h == '.')
199 return match_host(pattern, host);
200 host++;
dbb36e1b 201 }
16768191 202 return -1;
dbb36e1b
CMN
203 }
204
16768191 205 if (c != tolower(*host++))
dbb36e1b
CMN
206 return -1;
207 }
208
209 return -1;
210}
211
212static int check_host_name(const char *name, const char *host)
213{
214 if (!strcasecmp(name, host))
215 return 0;
216
217 if (match_host(name, host) < 0)
218 return -1;
219
220 return 0;
221}
222
223static int verify_server_cert(git_transport *t, const char *host)
224{
225 X509 *cert;
226 X509_NAME *peer_name;
441df990
CMN
227 ASN1_STRING *str;
228 unsigned char *peer_cn = NULL;
3f9eb1e5 229 int matched = -1, type = GEN_DNS;
dbb36e1b 230 GENERAL_NAMES *alts;
3f9eb1e5
CMN
231 struct in6_addr addr6;
232 struct in_addr addr4;
233 void *addr;
441df990
CMN
234 int i = -1,j;
235
3f9eb1e5
CMN
236
237 /* Try to parse the host as an IP address to see if it is */
238 if (inet_pton(AF_INET, host, &addr4)) {
239 type = GEN_IPADD;
240 addr = &addr4;
241 } else {
242 if(inet_pton(AF_INET6, host, &addr6)) {
243 type = GEN_IPADD;
244 addr = &addr6;
245 }
246 }
247
dbb36e1b
CMN
248
249 cert = SSL_get_peer_certificate(t->ssl.ssl);
250
251 /* Check the alternative names */
252 alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
253 if (alts) {
441df990 254 int num;
dbb36e1b
CMN
255
256 num = sk_GENERAL_NAME_num(alts);
257 for (i = 0; i < num && matched != 1; i++) {
258 const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
259 const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
260 size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
261
3f9eb1e5
CMN
262 /* Skip any names of a type we're not looking for */
263 if (gn->type != type)
dbb36e1b
CMN
264 continue;
265
3f9eb1e5
CMN
266 if (type == GEN_DNS) {
267 /* If it contains embedded NULs, don't even try */
441df990 268 if (memchr(name, '\0', namelen))
3f9eb1e5
CMN
269 continue;
270
271 if (check_host_name(name, host) < 0)
272 matched = 0;
273 else
274 matched = 1;
275 } else if (type == GEN_IPADD) {
276 /* Here name isn't so much a name but a binary representation of the IP */
277 matched = !!memcmp(name, addr, namelen);
278 }
dbb36e1b
CMN
279 }
280 }
281 GENERAL_NAMES_free(alts);
282
441df990
CMN
283 if (matched == 0)
284 goto on_error;
285
dbb36e1b
CMN
286 if (matched == 1)
287 return 0;
288
289 /* If no alternative names are available, check the common name */
290 peer_name = X509_get_subject_name(cert);
441df990
CMN
291 if (peer_name == NULL)
292 goto on_error;
293
294 if (peer_name) {
295 /* Get the index of the last CN entry */
296 while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
297 i = j;
298 }
299
300 if (i < 0)
301 goto on_error;
302
303 str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
304 if (str == NULL)
305 goto on_error;
306
307 /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
308 if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
309 int size = ASN1_STRING_length(str);
310
311 if (size > 0) {
312 peer_cn = OPENSSL_malloc(size + 1);
313 GITERR_CHECK_ALLOC(peer_cn);
314 memcpy(peer_cn, ASN1_STRING_data(str), size);
315 peer_cn[size] = '\0';
316 }
317 } else {
318 int size = ASN1_STRING_to_UTF8(&peer_cn, str);
319 GITERR_CHECK_ALLOC(peer_cn);
320 if (memchr(peer_cn, '\0', size))
321 goto cert_fail;
dbb36e1b
CMN
322 }
323
441df990
CMN
324 if (check_host_name((char *)peer_cn, host) < 0)
325 goto cert_fail;
326
327 OPENSSL_free(peer_cn);
328
dbb36e1b 329 return 0;
441df990
CMN
330
331on_error:
332 OPENSSL_free(peer_cn);
333 return ssl_set_error(&t->ssl, 0);
334
335cert_fail:
336 OPENSSL_free(peer_cn);
337 giterr_set(GITERR_SSL, "Certificate host name check failed");
338 return -1;
dbb36e1b 339}
dbb36e1b
CMN
340
341static int ssl_setup(git_transport *t, const char *host)
66024c7c 342{
a6f24a5b
CMN
343 int ret;
344
345 SSL_library_init();
346 SSL_load_error_strings();
347 t->ssl.ctx = SSL_CTX_new(SSLv23_method());
348 if (t->ssl.ctx == NULL)
349 return ssl_set_error(&t->ssl, 0);
350
351 SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY);
dbb36e1b
CMN
352 SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL);
353 if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx))
354 return ssl_set_error(&t->ssl, 0);
a6f24a5b
CMN
355
356 t->ssl.ssl = SSL_new(t->ssl.ctx);
357 if (t->ssl.ssl == NULL)
358 return ssl_set_error(&t->ssl, 0);
359
360 if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0)
361 return ssl_set_error(&t->ssl, ret);
362
363 if ((ret = SSL_connect(t->ssl.ssl)) <= 0)
364 return ssl_set_error(&t->ssl, ret);
365
250b95b2 366 if (t->check_cert && verify_server_cert(t, host) < 0)
dbb36e1b
CMN
367 return -1;
368
a6f24a5b 369 return 0;
d3e1367f 370}
89460f3f 371#else
d3e1367f
CMN
372static int ssl_setup(git_transport *t, const char *host)
373{
89460f3f 374 GIT_UNUSED(t);
d3e1367f 375 GIT_UNUSED(host);
89460f3f 376 return 0;
89460f3f 377}
d3e1367f
CMN
378#endif
379
66024c7c 380int gitno_connect(git_transport *t, const char *host, const char *port)
1b4f8140 381{
44ef8b1b 382 struct addrinfo *info = NULL, *p;
1b4f8140 383 struct addrinfo hints;
56b7df10 384 int ret;
44ef8b1b 385 GIT_SOCKET s = INVALID_SOCKET;
1b4f8140
CMN
386
387 memset(&hints, 0x0, sizeof(struct addrinfo));
1b4f8140 388 hints.ai_socktype = SOCK_STREAM;
a8df98c6 389 hints.ai_family = AF_UNSPEC;
1b4f8140 390
798e4d53
VM
391 if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
392 giterr_set(GITERR_NET,
393 "Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
cd58c15c 394 return -1;
1b4f8140
CMN
395 }
396
397 for (p = info; p != NULL; p = p->ai_next) {
398 s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
8d18f1f7 399
ccc9872d 400 if (s == INVALID_SOCKET) {
44ef8b1b
RB
401 net_set_error("error creating socket");
402 break;
1b4f8140
CMN
403 }
404
44ef8b1b
RB
405 if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
406 break;
1b4f8140 407
44ef8b1b
RB
408 /* If we can't connect, try the next one */
409 gitno_close(s);
410 s = INVALID_SOCKET;
1b4f8140
CMN
411 }
412
413 /* Oops, we couldn't connect to any address */
cd58c15c 414 if (s == INVALID_SOCKET && p == NULL) {
44ef8b1b 415 giterr_set(GITERR_OS, "Failed to connect to %s", host);
cd58c15c
VM
416 return -1;
417 }
44ef8b1b 418
66024c7c 419 t->socket = s;
798e4d53 420 p_freeaddrinfo(info);
66024c7c 421
0048372a 422 if (t->use_ssl && ssl_setup(t, host) < 0)
66024c7c 423 return -1;
66024c7c 424
cd58c15c 425 return 0;
1b4f8140 426}
4e95ef02 427
d3e1367f 428#ifdef GIT_SSL
a6f24a5b 429static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len)
4e95ef02 430{
0bd594b6
VM
431 int ret;
432 size_t off = 0;
4e95ef02
CMN
433
434 while (off < len) {
a6f24a5b
CMN
435 ret = SSL_write(ssl->ssl, msg + off, len - off);
436 if (ret <= 0)
437 return ssl_set_error(ssl, ret);
438
439 off += ret;
440 }
ccc9872d 441
a6f24a5b
CMN
442 return off;
443}
66024c7c
CMN
444#endif
445
446int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
447{
448 int ret;
449 size_t off = 0;
450
a6f24a5b 451#ifdef GIT_SSL
0048372a 452 if (t->use_ssl)
66024c7c
CMN
453 return send_ssl(&t->ssl, msg, len);
454#endif
455
456 while (off < len) {
457 errno = 0;
458 ret = p_send(t->socket, msg + off, len - off, flags);
56b7df10 459 if (ret < 0) {
bd6585a7 460 net_set_error("Error sending data");
56b7df10
CMN
461 return -1;
462 }
4e95ef02
CMN
463
464 off += ret;
465 }
466
44ef8b1b 467 return (int)off;
4e95ef02 468}
74bd343a 469
34bfb4b0 470
bad53552
CMN
471#ifdef GIT_WIN32
472int gitno_close(GIT_SOCKET s)
473{
474 return closesocket(s) == SOCKET_ERROR ? -1 : 0;
475}
476#else
477int gitno_close(GIT_SOCKET s)
478{
479 return close(s);
480}
481#endif
482
74bd343a
CMN
483int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
484{
485 fd_set fds;
486 struct timeval tv;
487
488 tv.tv_sec = sec;
489 tv.tv_usec = usec;
490
491 FD_ZERO(&fds);
492 FD_SET(buf->fd, &fds);
493
494 /* The select(2) interface is silly */
44ef8b1b 495 return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);
74bd343a 496}
db84b798
CMN
497
498int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
499{
500 char *colon, *slash, *delim;
db84b798
CMN
501
502 colon = strchr(url, ':');
503 slash = strchr(url, '/');
504
56b7df10 505 if (slash == NULL) {
44ef8b1b
RB
506 giterr_set(GITERR_NET, "Malformed URL: missing /");
507 return -1;
56b7df10 508 }
db84b798
CMN
509
510 if (colon == NULL) {
511 *port = git__strdup(default_port);
512 } else {
513 *port = git__strndup(colon + 1, slash - colon - 1);
514 }
56b7df10 515 GITERR_CHECK_ALLOC(*port);
db84b798
CMN
516
517 delim = colon == NULL ? slash : colon;
518 *host = git__strndup(url, delim - url);
56b7df10 519 GITERR_CHECK_ALLOC(*host);
db84b798 520
56b7df10 521 return 0;
db84b798 522}