]>
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> |
4e95ef02 | 13 | #else |
6e34111e VM |
14 | # include <winsock2.h> |
15 | # include <Ws2tcpip.h> | |
16 | # ifdef _MSC_VER | |
17 | # pragma comment(lib, "Ws2_32.lib") | |
18 | # endif | |
4e95ef02 | 19 | #endif |
1b4f8140 | 20 | |
66024c7c CMN |
21 | #ifdef GIT_GNUTLS |
22 | # include <gnutls/openssl.h> | |
23 | # include <gnutls/gnutls.h> | |
24 | # include <gnutls/x509.h> | |
a6f24a5b CMN |
25 | #elif defined(GIT_OPENSSL) |
26 | # include <openssl/ssl.h> | |
66024c7c | 27 | #endif |
6e34111e | 28 | |
a6f24a5b | 29 | |
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" |
66024c7c | 36 | #include "transport.h" |
bd6585a7 CMN |
37 | |
38 | #ifdef GIT_WIN32 | |
39 | static void net_set_error(const char *str) | |
40 | { | |
41 | int size, error = WSAGetLastError(); | |
42 | LPSTR err_str = NULL; | |
43 | ||
44 | size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, | |
45 | 0, error, 0, (LPSTR)&err_str, 0, 0); | |
46 | ||
65ca81a6 | 47 | giterr_set(GITERR_NET, "%s: %s", str, err_str); |
bd6585a7 CMN |
48 | LocalFree(err_str); |
49 | } | |
50 | #else | |
51 | static void net_set_error(const char *str) | |
52 | { | |
53 | giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); | |
54 | } | |
55 | #endif | |
1b4f8140 | 56 | |
66024c7c CMN |
57 | #ifdef GIT_GNUTLS |
58 | static int ssl_set_error(int error) | |
59 | { | |
60 | giterr_set(GITERR_NET, "SSL error: (%s) %s", gnutls_strerror_name(error), gnutls_strerror(error)); | |
61 | return -1; | |
62 | } | |
a6f24a5b CMN |
63 | #elif GIT_OPENSSL |
64 | static int ssl_set_error(gitno_ssl *ssl, int error) | |
65 | { | |
66 | int err; | |
67 | err = SSL_get_error(ssl->ssl, error); | |
68 | giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); | |
69 | return -1; | |
70 | } | |
66024c7c CMN |
71 | #endif |
72 | ||
73 | void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) | |
ea7a5452 CMN |
74 | { |
75 | memset(buf, 0x0, sizeof(gitno_buffer)); | |
76 | memset(data, 0x0, len); | |
77 | buf->data = data; | |
b0bda0a4 | 78 | buf->len = len; |
ea7a5452 | 79 | buf->offset = 0; |
66024c7c | 80 | buf->fd = t->socket; |
a6f24a5b | 81 | #ifdef GIT_SSL |
66024c7c | 82 | if (t->encrypt) |
a6f24a5b | 83 | buf->ssl = &t->ssl; |
66024c7c CMN |
84 | #endif |
85 | } | |
86 | ||
a6f24a5b | 87 | #ifdef GIT_GNUTLS |
66024c7c CMN |
88 | static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) |
89 | { | |
90 | int ret; | |
91 | ||
92 | do { | |
93 | ret = gnutls_record_recv(ssl->session, data, len); | |
94 | } while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); | |
95 | ||
96 | if (ret < 0) { | |
97 | ssl_set_error(ret); | |
98 | return -1; | |
99 | } | |
100 | ||
101 | return ret; | |
ea7a5452 | 102 | } |
a6f24a5b CMN |
103 | #elif defined(GIT_OPENSSL) |
104 | static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) | |
105 | { | |
106 | int ret; | |
107 | ||
108 | do { | |
109 | ret = SSL_read(ssl->ssl, data, len); | |
110 | } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); | |
111 | ||
112 | if (ret < 0) | |
113 | return ssl_set_error(ssl, ret); | |
114 | ||
115 | return ret; | |
116 | } | |
117 | #endif | |
ea7a5452 CMN |
118 | |
119 | int gitno_recv(gitno_buffer *buf) | |
120 | { | |
121 | int ret; | |
122 | ||
a6f24a5b | 123 | #ifdef GIT_SSL |
66024c7c CMN |
124 | if (buf->ssl != NULL) { |
125 | if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) | |
126 | return -1; | |
127 | } else { | |
128 | ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); | |
129 | if (ret < 0) { | |
130 | net_set_error("Error receiving socket data"); | |
131 | return -1; | |
132 | } | |
133 | } | |
134 | #else | |
44ef8b1b | 135 | ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); |
56b7df10 | 136 | if (ret < 0) { |
44ef8b1b | 137 | net_set_error("Error receiving socket data"); |
56b7df10 CMN |
138 | return -1; |
139 | } | |
66024c7c | 140 | #endif |
ea7a5452 CMN |
141 | |
142 | buf->offset += ret; | |
ea7a5452 CMN |
143 | return ret; |
144 | } | |
145 | ||
146 | /* Consume up to ptr and move the rest of the buffer to the beginning */ | |
c7c787ce | 147 | void gitno_consume(gitno_buffer *buf, const char *ptr) |
ea7a5452 | 148 | { |
0bd594b6 | 149 | size_t consumed; |
ea7a5452 | 150 | |
0bd594b6 | 151 | assert(ptr - buf->data >= 0); |
ea7a5452 CMN |
152 | assert(ptr - buf->data <= (int) buf->len); |
153 | ||
c7c787ce | 154 | consumed = ptr - buf->data; |
ea7a5452 | 155 | |
c7c787ce CMN |
156 | memmove(buf->data, ptr, buf->offset - consumed); |
157 | memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); | |
158 | buf->offset -= consumed; | |
ea7a5452 CMN |
159 | } |
160 | ||
161 | /* Consume const bytes and move the rest of the buffer to the beginning */ | |
0bd594b6 | 162 | void gitno_consume_n(gitno_buffer *buf, size_t cons) |
ea7a5452 CMN |
163 | { |
164 | memmove(buf->data, buf->data + cons, buf->len - buf->offset); | |
165 | memset(buf->data + cons, 0x0, buf->len - buf->offset); | |
166 | buf->offset -= cons; | |
167 | } | |
168 | ||
66024c7c CMN |
169 | #ifdef GIT_GNUTLS |
170 | static int ssl_setup(git_transport *t) | |
171 | { | |
172 | int ret; | |
173 | ||
174 | if ((ret = gnutls_global_init()) < 0) | |
175 | return ssl_set_error(ret); | |
176 | ||
177 | if ((ret = gnutls_certificate_allocate_credentials(&t->ssl.cred)) < 0) | |
178 | return ssl_set_error(ret); | |
179 | ||
180 | gnutls_init(&t->ssl.session, GNUTLS_CLIENT); | |
181 | //gnutls_certificate_set_verify_function(ssl->cred, SSL_VERIFY_NONE); | |
182 | gnutls_credentials_set(t->ssl.session, GNUTLS_CRD_CERTIFICATE, t->ssl.cred); | |
183 | ||
184 | if ((ret = gnutls_priority_set_direct (t->ssl.session, "NORMAL", NULL)) < 0) | |
185 | return ssl_set_error(ret); | |
186 | ||
187 | gnutls_transport_set_ptr(t->ssl.session, (gnutls_transport_ptr_t) t->socket); | |
188 | ||
189 | do { | |
190 | ret = gnutls_handshake(t->ssl.session); | |
191 | } while (ret < 0 && !gnutls_error_is_fatal(ret)); | |
192 | ||
193 | if (ret < 0) { | |
194 | ssl_set_error(ret); | |
195 | goto on_error; | |
196 | } | |
197 | ||
198 | return 0; | |
199 | ||
200 | on_error: | |
201 | gnutls_deinit(t->ssl.session); | |
202 | return -1; | |
203 | } | |
a6f24a5b CMN |
204 | #elif defined(GIT_OPENSSL) |
205 | static int ssl_setup(git_transport *t) | |
206 | { | |
207 | int ret; | |
208 | ||
209 | SSL_library_init(); | |
210 | SSL_load_error_strings(); | |
211 | t->ssl.ctx = SSL_CTX_new(SSLv23_method()); | |
212 | if (t->ssl.ctx == NULL) | |
213 | return ssl_set_error(&t->ssl, 0); | |
214 | ||
215 | SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); | |
216 | ||
217 | t->ssl.ssl = SSL_new(t->ssl.ctx); | |
218 | if (t->ssl.ssl == NULL) | |
219 | return ssl_set_error(&t->ssl, 0); | |
220 | ||
221 | if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) | |
222 | return ssl_set_error(&t->ssl, ret); | |
223 | ||
224 | if ((ret = SSL_connect(t->ssl.ssl)) <= 0) | |
225 | return ssl_set_error(&t->ssl, ret); | |
226 | ||
227 | return 0; | |
228 | } | |
66024c7c CMN |
229 | #endif |
230 | ||
231 | int gitno_connect(git_transport *t, const char *host, const char *port) | |
1b4f8140 | 232 | { |
44ef8b1b | 233 | struct addrinfo *info = NULL, *p; |
1b4f8140 | 234 | struct addrinfo hints; |
56b7df10 | 235 | int ret; |
44ef8b1b | 236 | GIT_SOCKET s = INVALID_SOCKET; |
1b4f8140 CMN |
237 | |
238 | memset(&hints, 0x0, sizeof(struct addrinfo)); | |
239 | hints.ai_family = AF_UNSPEC; | |
240 | hints.ai_socktype = SOCK_STREAM; | |
241 | ||
56b7df10 CMN |
242 | if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) { |
243 | giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret)); | |
cd58c15c | 244 | return -1; |
1b4f8140 CMN |
245 | } |
246 | ||
247 | for (p = info; p != NULL; p = p->ai_next) { | |
248 | s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); | |
ccc9872d | 249 | if (s == INVALID_SOCKET) { |
44ef8b1b RB |
250 | net_set_error("error creating socket"); |
251 | break; | |
1b4f8140 CMN |
252 | } |
253 | ||
44ef8b1b RB |
254 | if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) |
255 | break; | |
1b4f8140 | 256 | |
44ef8b1b RB |
257 | /* If we can't connect, try the next one */ |
258 | gitno_close(s); | |
259 | s = INVALID_SOCKET; | |
1b4f8140 CMN |
260 | } |
261 | ||
262 | /* Oops, we couldn't connect to any address */ | |
cd58c15c | 263 | if (s == INVALID_SOCKET && p == NULL) { |
44ef8b1b | 264 | giterr_set(GITERR_OS, "Failed to connect to %s", host); |
cd58c15c VM |
265 | return -1; |
266 | } | |
44ef8b1b | 267 | |
66024c7c | 268 | t->socket = s; |
44ef8b1b | 269 | freeaddrinfo(info); |
66024c7c | 270 | |
a6f24a5b | 271 | #ifdef GIT_SSL |
66024c7c CMN |
272 | if (t->encrypt && ssl_setup(t) < 0) |
273 | return -1; | |
274 | #endif | |
275 | ||
cd58c15c | 276 | return 0; |
1b4f8140 | 277 | } |
4e95ef02 | 278 | |
66024c7c CMN |
279 | #ifdef GIT_GNUTLS |
280 | static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) | |
4e95ef02 | 281 | { |
0bd594b6 VM |
282 | int ret; |
283 | size_t off = 0; | |
4e95ef02 CMN |
284 | |
285 | while (off < len) { | |
66024c7c CMN |
286 | ret = gnutls_record_send(ssl->session, msg + off, len - off); |
287 | if (ret < 0) { | |
288 | if (gnutls_error_is_fatal(ret)) | |
289 | return ssl_set_error(ret); | |
ccc9872d | 290 | |
66024c7c CMN |
291 | ret = 0; |
292 | } | |
293 | off += ret; | |
294 | } | |
295 | ||
296 | return off; | |
297 | } | |
a6f24a5b CMN |
298 | #elif defined(GIT_OPENSSL) |
299 | static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) | |
300 | { | |
301 | int ret; | |
302 | size_t off = 0; | |
303 | ||
304 | while (off < len) { | |
305 | ret = SSL_write(ssl->ssl, msg + off, len - off); | |
306 | if (ret <= 0) | |
307 | return ssl_set_error(ssl, ret); | |
308 | ||
309 | off += ret; | |
310 | } | |
311 | ||
312 | return off; | |
313 | } | |
66024c7c CMN |
314 | #endif |
315 | ||
316 | int gitno_send(git_transport *t, const char *msg, size_t len, int flags) | |
317 | { | |
318 | int ret; | |
319 | size_t off = 0; | |
320 | ||
a6f24a5b | 321 | #ifdef GIT_SSL |
66024c7c CMN |
322 | if (t->encrypt) |
323 | return send_ssl(&t->ssl, msg, len); | |
324 | #endif | |
325 | ||
326 | while (off < len) { | |
327 | errno = 0; | |
328 | ret = p_send(t->socket, msg + off, len - off, flags); | |
56b7df10 | 329 | if (ret < 0) { |
bd6585a7 | 330 | net_set_error("Error sending data"); |
56b7df10 CMN |
331 | return -1; |
332 | } | |
4e95ef02 CMN |
333 | |
334 | off += ret; | |
335 | } | |
336 | ||
44ef8b1b | 337 | return (int)off; |
4e95ef02 | 338 | } |
74bd343a | 339 | |
34bfb4b0 | 340 | |
bad53552 CMN |
341 | #ifdef GIT_WIN32 |
342 | int gitno_close(GIT_SOCKET s) | |
343 | { | |
344 | return closesocket(s) == SOCKET_ERROR ? -1 : 0; | |
345 | } | |
346 | #else | |
347 | int gitno_close(GIT_SOCKET s) | |
348 | { | |
349 | return close(s); | |
350 | } | |
351 | #endif | |
352 | ||
74bd343a CMN |
353 | int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) |
354 | { | |
355 | fd_set fds; | |
356 | struct timeval tv; | |
357 | ||
358 | tv.tv_sec = sec; | |
359 | tv.tv_usec = usec; | |
360 | ||
361 | FD_ZERO(&fds); | |
362 | FD_SET(buf->fd, &fds); | |
363 | ||
364 | /* The select(2) interface is silly */ | |
44ef8b1b | 365 | return select((int)buf->fd + 1, &fds, NULL, NULL, &tv); |
74bd343a | 366 | } |
db84b798 CMN |
367 | |
368 | int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) | |
369 | { | |
370 | char *colon, *slash, *delim; | |
db84b798 CMN |
371 | |
372 | colon = strchr(url, ':'); | |
373 | slash = strchr(url, '/'); | |
374 | ||
56b7df10 | 375 | if (slash == NULL) { |
44ef8b1b RB |
376 | giterr_set(GITERR_NET, "Malformed URL: missing /"); |
377 | return -1; | |
56b7df10 | 378 | } |
db84b798 CMN |
379 | |
380 | if (colon == NULL) { | |
381 | *port = git__strdup(default_port); | |
382 | } else { | |
383 | *port = git__strndup(colon + 1, slash - colon - 1); | |
384 | } | |
56b7df10 | 385 | GITERR_CHECK_ALLOC(*port); |
db84b798 CMN |
386 | |
387 | delim = colon == NULL ? slash : colon; | |
388 | *host = git__strndup(url, delim - url); | |
56b7df10 | 389 | GITERR_CHECK_ALLOC(*host); |
db84b798 | 390 | |
56b7df10 | 391 | return 0; |
db84b798 | 392 | } |