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