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