]>
Commit | Line | Data |
---|---|---|
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 | |
36 | static 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 | |
48 | static 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 |
55 | static 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 |
64 | int gitno_recv(gitno_buffer *buf) |
65 | { | |
66 | return buf->recv(buf); | |
66024c7c CMN |
67 | } |
68 | ||
d3e1367f | 69 | #ifdef GIT_SSL |
8861d32f | 70 | static 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 | 88 | int 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 |
102 | void 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 | ||
114 | void 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 | 126 | void 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 | 141 | void 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 |
148 | int 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 |
174 | static 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 | ||
212 | static 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 | ||
223 | static 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 | |
331 | on_error: | |
332 | OPENSSL_free(peer_cn); | |
333 | return ssl_set_error(&t->ssl, 0); | |
334 | ||
335 | cert_fail: | |
336 | OPENSSL_free(peer_cn); | |
337 | giterr_set(GITERR_SSL, "Certificate host name check failed"); | |
338 | return -1; | |
dbb36e1b | 339 | } |
dbb36e1b CMN |
340 | |
341 | static 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 |
372 | static 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 | 380 | int 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 | 429 | static 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 | ||
446 | int 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 |
472 | int gitno_close(GIT_SOCKET s) | |
473 | { | |
474 | return closesocket(s) == SOCKET_ERROR ? -1 : 0; | |
475 | } | |
476 | #else | |
477 | int gitno_close(GIT_SOCKET s) | |
478 | { | |
479 | return close(s); | |
480 | } | |
481 | #endif | |
482 | ||
74bd343a CMN |
483 | int 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 | |
498 | int 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 | } |