]>
Commit | Line | Data |
---|---|---|
1b4f8140 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
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> |
7a2cf780 | 13 | # include <netinet/in.h> |
66798ad0 | 14 | # include <arpa/inet.h> |
4e95ef02 | 15 | #else |
90c2b37f | 16 | # include <winsock2.h> |
c1318f71 | 17 | # include <ws2tcpip.h> |
6e34111e | 18 | # ifdef _MSC_VER |
41fb1ca0 | 19 | # pragma comment(lib, "ws2_32") |
6e34111e | 20 | # endif |
4e95ef02 | 21 | #endif |
1b4f8140 | 22 | |
d3e1367f | 23 | #ifdef GIT_SSL |
a6f24a5b | 24 | # include <openssl/ssl.h> |
41fb1ca0 | 25 | # include <openssl/err.h> |
dbb36e1b | 26 | # include <openssl/x509v3.h> |
66024c7c | 27 | #endif |
6e34111e | 28 | |
dbb36e1b | 29 | #include <ctype.h> |
1b4f8140 CMN |
30 | #include "git2/errors.h" |
31 | ||
32 | #include "common.h" | |
33 | #include "netops.h" | |
34bfb4b0 | 34 | #include "posix.h" |
bd6585a7 | 35 | #include "buffer.h" |
c227c173 | 36 | #include "http_parser.h" |
1d3364ac | 37 | #include "global.h" |
bd6585a7 CMN |
38 | |
39 | #ifdef GIT_WIN32 | |
40 | static void net_set_error(const char *str) | |
41 | { | |
b0dc81f0 | 42 | int error = WSAGetLastError(); |
c70455c7 | 43 | char * win32_error = git_win32_get_error_message(error); |
bd6585a7 | 44 | |
c70455c7 SS |
45 | if (win32_error) { |
46 | giterr_set(GITERR_NET, "%s: %s", str, win32_error); | |
47 | git__free(win32_error); | |
48 | } else { | |
49 | giterr_set(GITERR_NET, str); | |
bd25a302 | 50 | } |
bd6585a7 CMN |
51 | } |
52 | #else | |
53 | static void net_set_error(const char *str) | |
54 | { | |
55 | giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); | |
56 | } | |
57 | #endif | |
1b4f8140 | 58 | |
d3e1367f | 59 | #ifdef GIT_SSL |
a6f24a5b CMN |
60 | static int ssl_set_error(gitno_ssl *ssl, int error) |
61 | { | |
62 | int err; | |
65ac67fb MS |
63 | unsigned long e; |
64 | ||
a6f24a5b | 65 | err = SSL_get_error(ssl->ssl, error); |
65ac67fb MS |
66 | |
67 | assert(err != SSL_ERROR_WANT_READ); | |
68 | assert(err != SSL_ERROR_WANT_WRITE); | |
69 | ||
70 | switch (err) { | |
71 | case SSL_ERROR_WANT_CONNECT: | |
72 | case SSL_ERROR_WANT_ACCEPT: | |
73 | giterr_set(GITERR_NET, "SSL error: connection failure\n"); | |
74 | break; | |
75 | case SSL_ERROR_WANT_X509_LOOKUP: | |
76 | giterr_set(GITERR_NET, "SSL error: x509 error\n"); | |
77 | break; | |
78 | case SSL_ERROR_SYSCALL: | |
79 | e = ERR_get_error(); | |
80 | if (e > 0) { | |
81 | giterr_set(GITERR_NET, "SSL error: %s", | |
82 | ERR_error_string(e, NULL)); | |
83 | break; | |
84 | } else if (error < 0) { | |
85 | giterr_set(GITERR_OS, "SSL error: syscall failure"); | |
86 | break; | |
87 | } | |
88 | giterr_set(GITERR_NET, "SSL error: received early EOF"); | |
89 | break; | |
90 | case SSL_ERROR_SSL: | |
91 | e = ERR_get_error(); | |
92 | giterr_set(GITERR_NET, "SSL error: %s", | |
93 | ERR_error_string(e, NULL)); | |
94 | break; | |
95 | case SSL_ERROR_NONE: | |
96 | case SSL_ERROR_ZERO_RETURN: | |
97 | default: | |
98 | giterr_set(GITERR_NET, "SSL error: unknown error"); | |
99 | break; | |
100 | } | |
a6f24a5b CMN |
101 | return -1; |
102 | } | |
66024c7c CMN |
103 | #endif |
104 | ||
b49c8f71 CMN |
105 | int gitno_recv(gitno_buffer *buf) |
106 | { | |
107 | return buf->recv(buf); | |
66024c7c CMN |
108 | } |
109 | ||
d3e1367f | 110 | #ifdef GIT_SSL |
8861d32f | 111 | static int gitno__recv_ssl(gitno_buffer *buf) |
a6f24a5b CMN |
112 | { |
113 | int ret; | |
114 | ||
115 | do { | |
41fb1ca0 PK |
116 | ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset); |
117 | } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ); | |
a6f24a5b | 118 | |
8861d32f CMN |
119 | if (ret < 0) { |
120 | net_set_error("Error receiving socket data"); | |
121 | return -1; | |
122 | } | |
a6f24a5b | 123 | |
8861d32f | 124 | buf->offset += ret; |
a6f24a5b | 125 | return ret; |
ea7a5452 | 126 | } |
a6f24a5b | 127 | #endif |
ea7a5452 | 128 | |
41fb1ca0 | 129 | static int gitno__recv(gitno_buffer *buf) |
ea7a5452 CMN |
130 | { |
131 | int ret; | |
132 | ||
41fb1ca0 | 133 | ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0); |
56b7df10 | 134 | if (ret < 0) { |
44ef8b1b | 135 | net_set_error("Error receiving socket data"); |
56b7df10 CMN |
136 | return -1; |
137 | } | |
ea7a5452 CMN |
138 | |
139 | buf->offset += ret; | |
ea7a5452 CMN |
140 | return ret; |
141 | } | |
142 | ||
e25dda51 | 143 | void gitno_buffer_setup_callback( |
41fb1ca0 | 144 | gitno_socket *socket, |
e25dda51 VM |
145 | gitno_buffer *buf, |
146 | char *data, | |
147 | size_t len, | |
148 | int (*recv)(gitno_buffer *buf), void *cb_data) | |
8861d32f | 149 | { |
8861d32f CMN |
150 | memset(data, 0x0, len); |
151 | buf->data = data; | |
152 | buf->len = len; | |
153 | buf->offset = 0; | |
41fb1ca0 | 154 | buf->socket = socket; |
8861d32f CMN |
155 | buf->recv = recv; |
156 | buf->cb_data = cb_data; | |
157 | } | |
158 | ||
41fb1ca0 | 159 | void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) |
8861d32f CMN |
160 | { |
161 | #ifdef GIT_SSL | |
cf15ac8a | 162 | if (socket->ssl.ssl) { |
41fb1ca0 PK |
163 | gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); |
164 | return; | |
165 | } | |
8861d32f | 166 | #endif |
41fb1ca0 PK |
167 | |
168 | gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL); | |
8861d32f CMN |
169 | } |
170 | ||
ea7a5452 | 171 | /* Consume up to ptr and move the rest of the buffer to the beginning */ |
c7c787ce | 172 | void gitno_consume(gitno_buffer *buf, const char *ptr) |
ea7a5452 | 173 | { |
0bd594b6 | 174 | size_t consumed; |
ea7a5452 | 175 | |
0bd594b6 | 176 | assert(ptr - buf->data >= 0); |
ea7a5452 CMN |
177 | assert(ptr - buf->data <= (int) buf->len); |
178 | ||
c7c787ce | 179 | consumed = ptr - buf->data; |
ea7a5452 | 180 | |
c7c787ce CMN |
181 | memmove(buf->data, ptr, buf->offset - consumed); |
182 | memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); | |
183 | buf->offset -= consumed; | |
ea7a5452 CMN |
184 | } |
185 | ||
186 | /* Consume const bytes and move the rest of the buffer to the beginning */ | |
0bd594b6 | 187 | void gitno_consume_n(gitno_buffer *buf, size_t cons) |
ea7a5452 CMN |
188 | { |
189 | memmove(buf->data, buf->data + cons, buf->len - buf->offset); | |
190 | memset(buf->data + cons, 0x0, buf->len - buf->offset); | |
191 | buf->offset -= cons; | |
192 | } | |
193 | ||
d3e1367f | 194 | #ifdef GIT_SSL |
89460f3f | 195 | |
41fb1ca0 PK |
196 | static int gitno_ssl_teardown(gitno_ssl *ssl) |
197 | { | |
198 | int ret; | |
2f7538ec | 199 | |
f2b00cbd | 200 | ret = SSL_shutdown(ssl->ssl); |
89460f3f | 201 | if (ret < 0) |
2f7538ec PK |
202 | ret = ssl_set_error(ssl, ret); |
203 | else | |
204 | ret = 0; | |
89460f3f | 205 | |
41fb1ca0 | 206 | SSL_free(ssl->ssl); |
2f7538ec | 207 | return ret; |
89460f3f CMN |
208 | } |
209 | ||
7c57cd97 AM |
210 | #endif |
211 | ||
16768191 | 212 | /* Match host names according to RFC 2818 rules */ |
1f0d4f3d | 213 | int gitno__match_host(const char *pattern, const char *host) |
dbb36e1b CMN |
214 | { |
215 | for (;;) { | |
16768191 | 216 | char c = tolower(*pattern++); |
dbb36e1b CMN |
217 | |
218 | if (c == '\0') | |
219 | return *host ? -1 : 0; | |
220 | ||
221 | if (c == '*') { | |
222 | c = *pattern; | |
223 | /* '*' at the end matches everything left */ | |
224 | if (c == '\0') | |
225 | return 0; | |
226 | ||
16768191 CMN |
227 | /* |
228 | * We've found a pattern, so move towards the next matching | |
229 | * char. The '.' is handled specially because wildcards aren't | |
230 | * allowed to cross subdomains. | |
231 | */ | |
232 | ||
233 | while(*host) { | |
234 | char h = tolower(*host); | |
235 | if (c == h) | |
1f0d4f3d | 236 | return gitno__match_host(pattern, host++); |
16768191 | 237 | if (h == '.') |
1f0d4f3d | 238 | return gitno__match_host(pattern, host); |
16768191 | 239 | host++; |
dbb36e1b | 240 | } |
16768191 | 241 | return -1; |
dbb36e1b CMN |
242 | } |
243 | ||
16768191 | 244 | if (c != tolower(*host++)) |
dbb36e1b CMN |
245 | return -1; |
246 | } | |
247 | ||
248 | return -1; | |
249 | } | |
250 | ||
251 | static int check_host_name(const char *name, const char *host) | |
252 | { | |
253 | if (!strcasecmp(name, host)) | |
254 | return 0; | |
255 | ||
1f0d4f3d | 256 | if (gitno__match_host(name, host) < 0) |
dbb36e1b CMN |
257 | return -1; |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
7c57cd97 AM |
262 | #ifdef GIT_SSL |
263 | ||
41fb1ca0 | 264 | static int verify_server_cert(gitno_ssl *ssl, const char *host) |
dbb36e1b CMN |
265 | { |
266 | X509 *cert; | |
267 | X509_NAME *peer_name; | |
441df990 CMN |
268 | ASN1_STRING *str; |
269 | unsigned char *peer_cn = NULL; | |
3f9eb1e5 | 270 | int matched = -1, type = GEN_DNS; |
dbb36e1b | 271 | GENERAL_NAMES *alts; |
3f9eb1e5 CMN |
272 | struct in6_addr addr6; |
273 | struct in_addr addr4; | |
274 | void *addr; | |
441df990 CMN |
275 | int i = -1,j; |
276 | ||
41fb1ca0 | 277 | if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) { |
0d5dce26 CMN |
278 | giterr_set(GITERR_SSL, "The SSL certificate is invalid"); |
279 | return -1; | |
280 | } | |
3f9eb1e5 CMN |
281 | |
282 | /* Try to parse the host as an IP address to see if it is */ | |
345eef23 | 283 | if (p_inet_pton(AF_INET, host, &addr4)) { |
3f9eb1e5 CMN |
284 | type = GEN_IPADD; |
285 | addr = &addr4; | |
286 | } else { | |
345eef23 | 287 | if(p_inet_pton(AF_INET6, host, &addr6)) { |
3f9eb1e5 CMN |
288 | type = GEN_IPADD; |
289 | addr = &addr6; | |
290 | } | |
291 | } | |
292 | ||
dbb36e1b | 293 | |
41fb1ca0 | 294 | cert = SSL_get_peer_certificate(ssl->ssl); |
783555d8 CMN |
295 | if (!cert) { |
296 | giterr_set(GITERR_SSL, "the server did not provide a certificate"); | |
297 | return -1; | |
298 | } | |
dbb36e1b CMN |
299 | |
300 | /* Check the alternative names */ | |
301 | alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); | |
302 | if (alts) { | |
441df990 | 303 | int num; |
dbb36e1b CMN |
304 | |
305 | num = sk_GENERAL_NAME_num(alts); | |
306 | for (i = 0; i < num && matched != 1; i++) { | |
307 | const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); | |
308 | const char *name = (char *) ASN1_STRING_data(gn->d.ia5); | |
309 | size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); | |
310 | ||
3f9eb1e5 CMN |
311 | /* Skip any names of a type we're not looking for */ |
312 | if (gn->type != type) | |
dbb36e1b CMN |
313 | continue; |
314 | ||
3f9eb1e5 CMN |
315 | if (type == GEN_DNS) { |
316 | /* If it contains embedded NULs, don't even try */ | |
441df990 | 317 | if (memchr(name, '\0', namelen)) |
3f9eb1e5 CMN |
318 | continue; |
319 | ||
320 | if (check_host_name(name, host) < 0) | |
321 | matched = 0; | |
322 | else | |
323 | matched = 1; | |
324 | } else if (type == GEN_IPADD) { | |
325 | /* Here name isn't so much a name but a binary representation of the IP */ | |
326 | matched = !!memcmp(name, addr, namelen); | |
327 | } | |
dbb36e1b CMN |
328 | } |
329 | } | |
330 | GENERAL_NAMES_free(alts); | |
331 | ||
441df990 | 332 | if (matched == 0) |
51d3f6f5 | 333 | goto cert_fail_name; |
441df990 | 334 | |
dbb36e1b CMN |
335 | if (matched == 1) |
336 | return 0; | |
337 | ||
338 | /* If no alternative names are available, check the common name */ | |
339 | peer_name = X509_get_subject_name(cert); | |
441df990 CMN |
340 | if (peer_name == NULL) |
341 | goto on_error; | |
342 | ||
343 | if (peer_name) { | |
344 | /* Get the index of the last CN entry */ | |
345 | while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) | |
346 | i = j; | |
347 | } | |
348 | ||
349 | if (i < 0) | |
350 | goto on_error; | |
351 | ||
352 | str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); | |
353 | if (str == NULL) | |
354 | goto on_error; | |
355 | ||
356 | /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ | |
357 | if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { | |
358 | int size = ASN1_STRING_length(str); | |
359 | ||
360 | if (size > 0) { | |
361 | peer_cn = OPENSSL_malloc(size + 1); | |
362 | GITERR_CHECK_ALLOC(peer_cn); | |
363 | memcpy(peer_cn, ASN1_STRING_data(str), size); | |
364 | peer_cn[size] = '\0'; | |
365 | } | |
366 | } else { | |
367 | int size = ASN1_STRING_to_UTF8(&peer_cn, str); | |
368 | GITERR_CHECK_ALLOC(peer_cn); | |
369 | if (memchr(peer_cn, '\0', size)) | |
51d3f6f5 | 370 | goto cert_fail_name; |
dbb36e1b CMN |
371 | } |
372 | ||
441df990 | 373 | if (check_host_name((char *)peer_cn, host) < 0) |
51d3f6f5 | 374 | goto cert_fail_name; |
441df990 CMN |
375 | |
376 | OPENSSL_free(peer_cn); | |
377 | ||
dbb36e1b | 378 | return 0; |
441df990 CMN |
379 | |
380 | on_error: | |
381 | OPENSSL_free(peer_cn); | |
41fb1ca0 | 382 | return ssl_set_error(ssl, 0); |
441df990 | 383 | |
51d3f6f5 | 384 | cert_fail_name: |
441df990 | 385 | OPENSSL_free(peer_cn); |
51d3f6f5 | 386 | giterr_set(GITERR_SSL, "hostname does not match certificate"); |
9b940586 | 387 | return GIT_ECERTIFICATE; |
dbb36e1b | 388 | } |
dbb36e1b | 389 | |
41698f22 | 390 | static int ssl_setup(gitno_socket *socket, const char *host) |
1d3364ac CMN |
391 | { |
392 | int ret; | |
393 | ||
081e76ba CMN |
394 | if (git__ssl_ctx == NULL) { |
395 | giterr_set(GITERR_NET, "OpenSSL initialization failed"); | |
1d3364ac | 396 | return -1; |
081e76ba | 397 | } |
1d3364ac | 398 | |
cf15ac8a | 399 | socket->ssl.ssl = SSL_new(git__ssl_ctx); |
41fb1ca0 PK |
400 | if (socket->ssl.ssl == NULL) |
401 | return ssl_set_error(&socket->ssl, 0); | |
a6f24a5b | 402 | |
41fb1ca0 PK |
403 | if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0) |
404 | return ssl_set_error(&socket->ssl, ret); | |
a6f24a5b | 405 | |
41fb1ca0 PK |
406 | if ((ret = SSL_connect(socket->ssl.ssl)) <= 0) |
407 | return ssl_set_error(&socket->ssl, ret); | |
a6f24a5b | 408 | |
9c8dbc88 | 409 | return verify_server_cert(&socket->ssl, host); |
d3e1367f | 410 | } |
41fb1ca0 PK |
411 | #endif |
412 | ||
413 | static int gitno__close(GIT_SOCKET s) | |
d3e1367f | 414 | { |
41fb1ca0 PK |
415 | #ifdef GIT_WIN32 |
416 | if (SOCKET_ERROR == closesocket(s)) | |
417 | return -1; | |
418 | ||
419 | if (0 != WSACleanup()) { | |
420 | giterr_set(GITERR_OS, "Winsock cleanup failed"); | |
421 | return -1; | |
422 | } | |
423 | ||
89460f3f | 424 | return 0; |
41fb1ca0 PK |
425 | #else |
426 | return close(s); | |
d3e1367f | 427 | #endif |
41fb1ca0 | 428 | } |
d3e1367f | 429 | |
41fb1ca0 | 430 | int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags) |
1b4f8140 | 431 | { |
44ef8b1b | 432 | struct addrinfo *info = NULL, *p; |
1b4f8140 | 433 | struct addrinfo hints; |
44ef8b1b | 434 | GIT_SOCKET s = INVALID_SOCKET; |
41fb1ca0 PK |
435 | int ret; |
436 | ||
437 | #ifdef GIT_WIN32 | |
438 | /* on win32, the WSA context needs to be initialized | |
439 | * before any socket calls can be performed */ | |
440 | WSADATA wsd; | |
441 | ||
442 | if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { | |
443 | giterr_set(GITERR_OS, "Winsock init failed"); | |
444 | return -1; | |
445 | } | |
446 | ||
447 | if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { | |
448 | WSACleanup(); | |
449 | giterr_set(GITERR_OS, "Winsock init failed"); | |
450 | return -1; | |
451 | } | |
452 | #endif | |
453 | ||
454 | /* Zero the socket structure provided */ | |
455 | memset(s_out, 0x0, sizeof(gitno_socket)); | |
1b4f8140 CMN |
456 | |
457 | memset(&hints, 0x0, sizeof(struct addrinfo)); | |
1b4f8140 | 458 | hints.ai_socktype = SOCK_STREAM; |
a8df98c6 | 459 | hints.ai_family = AF_UNSPEC; |
1b4f8140 | 460 | |
798e4d53 VM |
461 | if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) { |
462 | giterr_set(GITERR_NET, | |
463 | "Failed to resolve address for %s: %s", host, p_gai_strerror(ret)); | |
cd58c15c | 464 | return -1; |
1b4f8140 CMN |
465 | } |
466 | ||
467 | for (p = info; p != NULL; p = p->ai_next) { | |
468 | s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); | |
8d18f1f7 | 469 | |
ccc9872d | 470 | if (s == INVALID_SOCKET) { |
44ef8b1b RB |
471 | net_set_error("error creating socket"); |
472 | break; | |
1b4f8140 CMN |
473 | } |
474 | ||
44ef8b1b RB |
475 | if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) |
476 | break; | |
1b4f8140 | 477 | |
44ef8b1b | 478 | /* If we can't connect, try the next one */ |
41fb1ca0 | 479 | gitno__close(s); |
44ef8b1b | 480 | s = INVALID_SOCKET; |
1b4f8140 CMN |
481 | } |
482 | ||
483 | /* Oops, we couldn't connect to any address */ | |
cd58c15c | 484 | if (s == INVALID_SOCKET && p == NULL) { |
44ef8b1b | 485 | giterr_set(GITERR_OS, "Failed to connect to %s", host); |
cfc39f50 | 486 | p_freeaddrinfo(info); |
cd58c15c VM |
487 | return -1; |
488 | } | |
44ef8b1b | 489 | |
41fb1ca0 | 490 | s_out->socket = s; |
798e4d53 | 491 | p_freeaddrinfo(info); |
66024c7c | 492 | |
41fb1ca0 | 493 | #ifdef GIT_SSL |
9b940586 | 494 | if ((flags & GITNO_CONNECT_SSL) && |
41698f22 | 495 | (ret = ssl_setup(s_out, host)) < 0) |
9b940586 | 496 | return ret; |
41fb1ca0 PK |
497 | #else |
498 | /* SSL is not supported */ | |
499 | if (flags & GITNO_CONNECT_SSL) { | |
500 | giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2."); | |
501 | return -1; | |
502 | } | |
503 | #endif | |
66024c7c | 504 | |
cd58c15c | 505 | return 0; |
1b4f8140 | 506 | } |
4e95ef02 | 507 | |
d3e1367f | 508 | #ifdef GIT_SSL |
41fb1ca0 | 509 | static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags) |
4e95ef02 | 510 | { |
0bd594b6 VM |
511 | int ret; |
512 | size_t off = 0; | |
4e95ef02 | 513 | |
41fb1ca0 PK |
514 | GIT_UNUSED(flags); |
515 | ||
4e95ef02 | 516 | while (off < len) { |
a6f24a5b | 517 | ret = SSL_write(ssl->ssl, msg + off, len - off); |
4deda91b | 518 | if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) |
a6f24a5b CMN |
519 | return ssl_set_error(ssl, ret); |
520 | ||
521 | off += ret; | |
41fb1ca0 | 522 | } |
ccc9872d | 523 | |
a6f24a5b CMN |
524 | return off; |
525 | } | |
66024c7c CMN |
526 | #endif |
527 | ||
41fb1ca0 | 528 | int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) |
66024c7c CMN |
529 | { |
530 | int ret; | |
531 | size_t off = 0; | |
532 | ||
a6f24a5b | 533 | #ifdef GIT_SSL |
cf15ac8a | 534 | if (socket->ssl.ssl) |
41fb1ca0 | 535 | return gitno_send_ssl(&socket->ssl, msg, len, flags); |
66024c7c CMN |
536 | #endif |
537 | ||
538 | while (off < len) { | |
539 | errno = 0; | |
41fb1ca0 | 540 | ret = p_send(socket->socket, msg + off, len - off, flags); |
56b7df10 | 541 | if (ret < 0) { |
bd6585a7 | 542 | net_set_error("Error sending data"); |
56b7df10 CMN |
543 | return -1; |
544 | } | |
4e95ef02 CMN |
545 | |
546 | off += ret; | |
547 | } | |
548 | ||
44ef8b1b | 549 | return (int)off; |
4e95ef02 | 550 | } |
74bd343a | 551 | |
41fb1ca0 | 552 | int gitno_close(gitno_socket *s) |
bad53552 | 553 | { |
41fb1ca0 | 554 | #ifdef GIT_SSL |
cf15ac8a | 555 | if (s->ssl.ssl && |
41fb1ca0 PK |
556 | gitno_ssl_teardown(&s->ssl) < 0) |
557 | return -1; | |
bad53552 CMN |
558 | #endif |
559 | ||
41fb1ca0 PK |
560 | return gitno__close(s->socket); |
561 | } | |
562 | ||
74bd343a CMN |
563 | int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) |
564 | { | |
565 | fd_set fds; | |
566 | struct timeval tv; | |
567 | ||
568 | tv.tv_sec = sec; | |
569 | tv.tv_usec = usec; | |
570 | ||
571 | FD_ZERO(&fds); | |
41fb1ca0 | 572 | FD_SET(buf->socket->socket, &fds); |
74bd343a CMN |
573 | |
574 | /* The select(2) interface is silly */ | |
41fb1ca0 | 575 | return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); |
74bd343a | 576 | } |
db84b798 | 577 | |
8988688c BS |
578 | static const char *prefix_http = "http://"; |
579 | static const char *prefix_https = "https://"; | |
580 | ||
581 | int gitno_connection_data_from_url( | |
582 | gitno_connection_data *data, | |
583 | const char *url, | |
ea59f659 | 584 | const char *service_suffix) |
8988688c | 585 | { |
ea59f659 | 586 | int error = -1; |
c227c173 | 587 | const char *default_port = NULL, *path_search_start = NULL; |
ea59f659 | 588 | char *original_host = NULL; |
8988688c BS |
589 | |
590 | /* service_suffix is optional */ | |
591 | assert(data && url); | |
592 | ||
ea59f659 | 593 | /* Save these for comparison later */ |
b59344bf BS |
594 | original_host = data->host; |
595 | data->host = NULL; | |
ea59f659 BS |
596 | gitno_connection_data_free_ptrs(data); |
597 | ||
8988688c | 598 | if (!git__prefixcmp(url, prefix_http)) { |
c227c173 | 599 | path_search_start = url + strlen(prefix_http); |
8988688c BS |
600 | default_port = "80"; |
601 | ||
602 | if (data->use_ssl) { | |
603 | giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed"); | |
ea59f659 | 604 | goto cleanup; |
8988688c | 605 | } |
c227c173 BS |
606 | } else if (!git__prefixcmp(url, prefix_https)) { |
607 | path_search_start = url + strlen(prefix_https); | |
8988688c BS |
608 | default_port = "443"; |
609 | data->use_ssl = true; | |
c227c173 | 610 | } else if (url[0] == '/') |
41a6de28 BS |
611 | default_port = data->use_ssl ? "443" : "80"; |
612 | ||
8988688c BS |
613 | if (!default_port) { |
614 | giterr_set(GITERR_NET, "Unrecognized URL prefix"); | |
ea59f659 | 615 | goto cleanup; |
8988688c BS |
616 | } |
617 | ||
618 | error = gitno_extract_url_parts( | |
c227c173 | 619 | &data->host, &data->port, &data->path, &data->user, &data->pass, |
8988688c BS |
620 | url, default_port); |
621 | ||
41a6de28 BS |
622 | if (url[0] == '/') { |
623 | /* Relative redirect; reuse original host name and port */ | |
c227c173 | 624 | path_search_start = url; |
41a6de28 BS |
625 | git__free(data->host); |
626 | data->host = original_host; | |
627 | original_host = NULL; | |
628 | } | |
629 | ||
8988688c | 630 | if (!error) { |
c227c173 | 631 | const char *path = strchr(path_search_start, '/'); |
8988688c BS |
632 | size_t pathlen = strlen(path); |
633 | size_t suffixlen = service_suffix ? strlen(service_suffix) : 0; | |
634 | ||
635 | if (suffixlen && | |
e1ce5249 CMN |
636 | !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) { |
637 | git__free(data->path); | |
8988688c | 638 | data->path = git__strndup(path, pathlen - suffixlen); |
e1ce5249 CMN |
639 | } else { |
640 | git__free(data->path); | |
8988688c | 641 | data->path = git__strdup(path); |
e1ce5249 | 642 | } |
8988688c BS |
643 | |
644 | /* Check for errors in the resulting data */ | |
8988688c BS |
645 | if (original_host && url[0] != '/' && strcmp(original_host, data->host)) { |
646 | giterr_set(GITERR_NET, "Cross host redirect not allowed"); | |
647 | error = -1; | |
648 | } | |
649 | } | |
650 | ||
ea59f659 BS |
651 | cleanup: |
652 | if (original_host) git__free(original_host); | |
8988688c BS |
653 | return error; |
654 | } | |
655 | ||
656 | void gitno_connection_data_free_ptrs(gitno_connection_data *d) | |
657 | { | |
658 | git__free(d->host); d->host = NULL; | |
659 | git__free(d->port); d->port = NULL; | |
660 | git__free(d->path); d->path = NULL; | |
661 | git__free(d->user); d->user = NULL; | |
662 | git__free(d->pass); d->pass = NULL; | |
663 | } | |
664 | ||
79c44342 | 665 | #define hex2c(c) ((c | 32) % 39 - 9) |
16bffd1c BS |
666 | static char* unescape(char *str) |
667 | { | |
668 | int x, y; | |
98eaf39a | 669 | int len = (int)strlen(str); |
16bffd1c | 670 | |
79c44342 | 671 | for (x=y=0; str[y]; ++x, ++y) { |
16bffd1c | 672 | if ((str[x] = str[y]) == '%') { |
79c44342 BS |
673 | if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) { |
674 | str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]); | |
675 | y += 2; | |
676 | } | |
16bffd1c BS |
677 | } |
678 | } | |
679 | str[x] = '\0'; | |
680 | return str; | |
681 | } | |
682 | ||
cf7038a6 BS |
683 | int gitno_extract_url_parts( |
684 | char **host, | |
685 | char **port, | |
c227c173 | 686 | char **path, |
cf7038a6 BS |
687 | char **username, |
688 | char **password, | |
689 | const char *url, | |
690 | const char *default_port) | |
db84b798 | 691 | { |
c227c173 BS |
692 | struct http_parser_url u = {0}; |
693 | const char *_host, *_port, *_path, *_userinfo; | |
db84b798 | 694 | |
c227c173 BS |
695 | if (http_parser_parse_url(url, strlen(url), false, &u)) { |
696 | giterr_set(GITERR_NET, "Malformed URL '%s'", url); | |
697 | return GIT_EINVALIDSPEC; | |
7e035908 | 698 | } |
db84b798 | 699 | |
c227c173 BS |
700 | _host = url+u.field_data[UF_HOST].off; |
701 | _port = url+u.field_data[UF_PORT].off; | |
702 | _path = url+u.field_data[UF_PATH].off; | |
703 | _userinfo = url+u.field_data[UF_USERINFO].off; | |
704 | ||
fe294b95 | 705 | if (u.field_set & (1 << UF_HOST)) { |
c227c173 BS |
706 | *host = git__substrdup(_host, u.field_data[UF_HOST].len); |
707 | GITERR_CHECK_ALLOC(*host); | |
56b7df10 | 708 | } |
db84b798 | 709 | |
fe294b95 | 710 | if (u.field_set & (1 << UF_PORT)) |
c227c173 BS |
711 | *port = git__substrdup(_port, u.field_data[UF_PORT].len); |
712 | else | |
7e035908 | 713 | *port = git__strdup(default_port); |
7e035908 | 714 | GITERR_CHECK_ALLOC(*port); |
db84b798 | 715 | |
fe294b95 | 716 | if (u.field_set & (1 << UF_PATH)) { |
c227c173 BS |
717 | *path = git__substrdup(_path, u.field_data[UF_PATH].len); |
718 | GITERR_CHECK_ALLOC(*path); | |
1380e7c6 CMN |
719 | } else { |
720 | giterr_set(GITERR_NET, "invalid url, missing path"); | |
721 | return GIT_EINVALIDSPEC; | |
c227c173 BS |
722 | } |
723 | ||
fe294b95 BS |
724 | if (u.field_set & (1 << UF_USERINFO)) { |
725 | const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len); | |
d6eb3f9c | 726 | if (colon) { |
16bffd1c BS |
727 | *username = unescape(git__substrdup(_userinfo, colon - _userinfo)); |
728 | *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo))); | |
c227c173 BS |
729 | GITERR_CHECK_ALLOC(*password); |
730 | } else { | |
731 | *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len); | |
732 | } | |
733 | GITERR_CHECK_ALLOC(*username); | |
16bffd1c | 734 | |
c227c173 BS |
735 | } |
736 | ||
56b7df10 | 737 | return 0; |
db84b798 | 738 | } |