1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
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 
8 #include "smart.h"
9 
10 #include "git2.h"
11 #include "refs.h"
12 #include "refspec.h"
13 #include "proxy.h"
14 
git_smart__recv_cb(gitno_buffer * buf)15 static int git_smart__recv_cb(gitno_buffer *buf)
16 {
17 	transport_smart *t = (transport_smart *) buf->cb_data;
18 	size_t old_len, bytes_read;
19 	int error;
20 
21 	GIT_ASSERT(t->current_stream);
22 
23 	old_len = buf->offset;
24 
25 	if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
26 		return error;
27 
28 	buf->offset += bytes_read;
29 
30 	if (t->packetsize_cb && !t->cancelled.val) {
31 		error = t->packetsize_cb(bytes_read, t->packetsize_payload);
32 		if (error) {
33 			git_atomic32_set(&t->cancelled, 1);
34 			return GIT_EUSER;
35 		}
36 	}
37 
38 	return (int)(buf->offset - old_len);
39 }
40 
git_smart__reset_stream(transport_smart * t,bool close_subtransport)41 GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
42 {
43 	if (t->current_stream) {
44 		t->current_stream->free(t->current_stream);
45 		t->current_stream = NULL;
46 	}
47 
48 	if (close_subtransport) {
49 		git__free(t->url);
50 		t->url = NULL;
51 
52 		if (t->wrapped->close(t->wrapped) < 0)
53 			return -1;
54 	}
55 
56 	return 0;
57 }
58 
git_smart__set_callbacks(git_transport * transport,git_transport_message_cb progress_cb,git_transport_message_cb error_cb,git_transport_certificate_check_cb certificate_check_cb,void * message_cb_payload)59 static int git_smart__set_callbacks(
60 	git_transport *transport,
61 	git_transport_message_cb progress_cb,
62 	git_transport_message_cb error_cb,
63 	git_transport_certificate_check_cb certificate_check_cb,
64 	void *message_cb_payload)
65 {
66 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
67 
68 	t->progress_cb = progress_cb;
69 	t->error_cb = error_cb;
70 	t->certificate_check_cb = certificate_check_cb;
71 	t->message_cb_payload = message_cb_payload;
72 
73 	return 0;
74 }
75 
http_header_name_length(const char * http_header)76 static size_t http_header_name_length(const char *http_header)
77 {
78 	const char *colon = strchr(http_header, ':');
79 	if (!colon)
80 		return 0;
81 	return colon - http_header;
82 }
83 
is_malformed_http_header(const char * http_header)84 static bool is_malformed_http_header(const char *http_header)
85 {
86 	const char *c;
87 	size_t name_len;
88 
89 	/* Disallow \r and \n */
90 	c = strchr(http_header, '\r');
91 	if (c)
92 		return true;
93 	c = strchr(http_header, '\n');
94 	if (c)
95 		return true;
96 
97 	/* Require a header name followed by : */
98 	name_len = http_header_name_length(http_header);
99 	if (name_len < 1)
100 		return true;
101 
102 	return false;
103 }
104 
105 static char *forbidden_custom_headers[] = {
106 	"User-Agent",
107 	"Host",
108 	"Accept",
109 	"Content-Type",
110 	"Transfer-Encoding",
111 	"Content-Length",
112 };
113 
is_forbidden_custom_header(const char * custom_header)114 static bool is_forbidden_custom_header(const char *custom_header)
115 {
116 	unsigned long i;
117 	size_t name_len = http_header_name_length(custom_header);
118 
119 	/* Disallow headers that we set */
120 	for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++)
121 		if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0)
122 			return true;
123 
124 	return false;
125 }
126 
git_smart__set_custom_headers(git_transport * transport,const git_strarray * custom_headers)127 static int git_smart__set_custom_headers(
128 	git_transport *transport,
129 	const git_strarray *custom_headers)
130 {
131 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
132 	size_t i;
133 
134 	if (t->custom_headers.count)
135 		git_strarray_dispose(&t->custom_headers);
136 
137 	if (!custom_headers)
138 		return 0;
139 
140 	for (i = 0; i < custom_headers->count; i++) {
141 		if (is_malformed_http_header(custom_headers->strings[i])) {
142 			git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]);
143 			return -1;
144 		}
145 		if (is_forbidden_custom_header(custom_headers->strings[i])) {
146 			git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]);
147 			return -1;
148 		}
149 	}
150 
151 	return git_strarray_copy(&t->custom_headers, custom_headers);
152 }
153 
git_smart__update_heads(transport_smart * t,git_vector * symrefs)154 int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
155 {
156 	size_t i;
157 	git_pkt *pkt;
158 
159 	git_vector_clear(&t->heads);
160 	git_vector_foreach(&t->refs, i, pkt) {
161 		git_pkt_ref *ref = (git_pkt_ref *) pkt;
162 		if (pkt->type != GIT_PKT_REF)
163 			continue;
164 
165 		if (symrefs) {
166 			git_refspec *spec;
167 			git_buf buf = GIT_BUF_INIT;
168 			size_t j;
169 			int error = 0;
170 
171 			git_vector_foreach(symrefs, j, spec) {
172 				git_buf_clear(&buf);
173 				if (git_refspec_src_matches(spec, ref->head.name) &&
174 				    !(error = git_refspec_transform(&buf, spec, ref->head.name))) {
175 					git__free(ref->head.symref_target);
176 					ref->head.symref_target = git_buf_detach(&buf);
177 				}
178 			}
179 
180 			git_buf_dispose(&buf);
181 
182 			if (error < 0)
183 				return error;
184 		}
185 
186 		if (git_vector_insert(&t->heads, &ref->head) < 0)
187 			return -1;
188 	}
189 
190 	return 0;
191 }
192 
free_symrefs(git_vector * symrefs)193 static void free_symrefs(git_vector *symrefs)
194 {
195 	git_refspec *spec;
196 	size_t i;
197 
198 	git_vector_foreach(symrefs, i, spec) {
199 		git_refspec__dispose(spec);
200 		git__free(spec);
201 	}
202 
203 	git_vector_free(symrefs);
204 }
205 
git_smart__connect(git_transport * transport,const char * url,git_credential_acquire_cb cred_acquire_cb,void * cred_acquire_payload,const git_proxy_options * proxy,int direction,int flags)206 static int git_smart__connect(
207 	git_transport *transport,
208 	const char *url,
209 	git_credential_acquire_cb cred_acquire_cb,
210 	void *cred_acquire_payload,
211 	const git_proxy_options *proxy,
212 	int direction,
213 	int flags)
214 {
215 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
216 	git_smart_subtransport_stream *stream;
217 	int error;
218 	git_pkt *pkt;
219 	git_pkt_ref *first;
220 	git_vector symrefs;
221 	git_smart_service_t service;
222 
223 	if (git_smart__reset_stream(t, true) < 0)
224 		return -1;
225 
226 	t->url = git__strdup(url);
227 	GIT_ERROR_CHECK_ALLOC(t->url);
228 
229 	git_proxy_options_clear(&t->proxy);
230 
231 	if (git_proxy_options_dup(&t->proxy, proxy) < 0)
232 		return -1;
233 
234 	t->direction = direction;
235 	t->flags = flags;
236 	t->cred_acquire_cb = cred_acquire_cb;
237 	t->cred_acquire_payload = cred_acquire_payload;
238 
239 	if (GIT_DIRECTION_FETCH == t->direction)
240 		service = GIT_SERVICE_UPLOADPACK_LS;
241 	else if (GIT_DIRECTION_PUSH == t->direction)
242 		service = GIT_SERVICE_RECEIVEPACK_LS;
243 	else {
244 		git_error_set(GIT_ERROR_NET, "invalid direction");
245 		return -1;
246 	}
247 
248 	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
249 		return error;
250 
251 	/* Save off the current stream (i.e. socket) that we are working with */
252 	t->current_stream = stream;
253 
254 	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
255 
256 	/* 2 flushes for RPC; 1 for stateful */
257 	if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
258 		return error;
259 
260 	/* Strip the comment packet for RPC */
261 	if (t->rpc) {
262 		pkt = (git_pkt *)git_vector_get(&t->refs, 0);
263 
264 		if (!pkt || GIT_PKT_COMMENT != pkt->type) {
265 			git_error_set(GIT_ERROR_NET, "invalid response");
266 			return -1;
267 		} else {
268 			/* Remove the comment pkt from the list */
269 			git_vector_remove(&t->refs, 0);
270 			git__free(pkt);
271 		}
272 	}
273 
274 	/* We now have loaded the refs. */
275 	t->have_refs = 1;
276 
277 	pkt = (git_pkt *)git_vector_get(&t->refs, 0);
278 	if (pkt && GIT_PKT_REF != pkt->type) {
279 		git_error_set(GIT_ERROR_NET, "invalid response");
280 		return -1;
281 	}
282 	first = (git_pkt_ref *)pkt;
283 
284 	if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
285 		return error;
286 
287 	/* Detect capabilities */
288 	if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) {
289 		/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
290 		if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
291 			git_oid_is_zero(&first->head.oid)) {
292 			git_vector_clear(&t->refs);
293 			git_pkt_free((git_pkt *)first);
294 		}
295 
296 		/* Keep a list of heads for _ls */
297 		git_smart__update_heads(t, &symrefs);
298 	} else if (error == GIT_ENOTFOUND) {
299 		/* There was no ref packet received, or the cap list was empty */
300 		error = 0;
301 	} else {
302 		git_error_set(GIT_ERROR_NET, "invalid response");
303 		goto cleanup;
304 	}
305 
306 	if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0)
307 		goto cleanup;
308 
309 	/* We're now logically connected. */
310 	t->connected = 1;
311 
312 cleanup:
313 	free_symrefs(&symrefs);
314 
315 	return error;
316 }
317 
git_smart__ls(const git_remote_head *** out,size_t * size,git_transport * transport)318 static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
319 {
320 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
321 
322 	if (!t->have_refs) {
323 		git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
324 		return -1;
325 	}
326 
327 	*out = (const git_remote_head **) t->heads.contents;
328 	*size = t->heads.length;
329 
330 	return 0;
331 }
332 
git_smart__negotiation_step(git_transport * transport,void * data,size_t len)333 int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
334 {
335 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
336 	git_smart_subtransport_stream *stream;
337 	int error;
338 
339 	if (t->rpc && git_smart__reset_stream(t, false) < 0)
340 		return -1;
341 
342 	if (GIT_DIRECTION_FETCH != t->direction) {
343 		git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch");
344 		return -1;
345 	}
346 
347 	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
348 		return error;
349 
350 	/* If this is a stateful implementation, the stream we get back should be the same */
351 	GIT_ASSERT(t->rpc || t->current_stream == stream);
352 
353 	/* Save off the current stream (i.e. socket) that we are working with */
354 	t->current_stream = stream;
355 
356 	if ((error = stream->write(stream, (const char *)data, len)) < 0)
357 		return error;
358 
359 	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
360 
361 	return 0;
362 }
363 
git_smart__get_push_stream(transport_smart * t,git_smart_subtransport_stream ** stream)364 int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
365 {
366 	int error;
367 
368 	if (t->rpc && git_smart__reset_stream(t, false) < 0)
369 		return -1;
370 
371 	if (GIT_DIRECTION_PUSH != t->direction) {
372 		git_error_set(GIT_ERROR_NET, "this operation is only valid for push");
373 		return -1;
374 	}
375 
376 	if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
377 		return error;
378 
379 	/* If this is a stateful implementation, the stream we get back should be the same */
380 	GIT_ASSERT(t->rpc || t->current_stream == *stream);
381 
382 	/* Save off the current stream (i.e. socket) that we are working with */
383 	t->current_stream = *stream;
384 
385 	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
386 
387 	return 0;
388 }
389 
git_smart__cancel(git_transport * transport)390 static void git_smart__cancel(git_transport *transport)
391 {
392 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
393 
394 	git_atomic32_set(&t->cancelled, 1);
395 }
396 
git_smart__is_connected(git_transport * transport)397 static int git_smart__is_connected(git_transport *transport)
398 {
399 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
400 
401 	return t->connected;
402 }
403 
git_smart__read_flags(git_transport * transport,int * flags)404 static int git_smart__read_flags(git_transport *transport, int *flags)
405 {
406 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
407 
408 	*flags = t->flags;
409 
410 	return 0;
411 }
412 
git_smart__close(git_transport * transport)413 static int git_smart__close(git_transport *transport)
414 {
415 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
416 	git_vector *common = &t->common;
417 	unsigned int i;
418 	git_pkt *p;
419 	int ret;
420 	git_smart_subtransport_stream *stream;
421 	const char flush[] = "0000";
422 
423 	/*
424 	 * If we're still connected at this point and not using RPC,
425 	 * we should say goodbye by sending a flush, or git-daemon
426 	 * will complain that we disconnected unexpectedly.
427 	 */
428 	if (t->connected && !t->rpc &&
429 	    !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
430 		t->current_stream->write(t->current_stream, flush, 4);
431 	}
432 
433 	ret = git_smart__reset_stream(t, true);
434 
435 	git_vector_foreach(common, i, p)
436 		git_pkt_free(p);
437 
438 	git_vector_free(common);
439 
440 	if (t->url) {
441 		git__free(t->url);
442 		t->url = NULL;
443 	}
444 
445 	t->connected = 0;
446 
447 	return ret;
448 }
449 
git_smart__free(git_transport * transport)450 static void git_smart__free(git_transport *transport)
451 {
452 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
453 	git_vector *refs = &t->refs;
454 	unsigned int i;
455 	git_pkt *p;
456 
457 	/* Make sure that the current stream is closed, if we have one. */
458 	git_smart__close(transport);
459 
460 	/* Free the subtransport */
461 	t->wrapped->free(t->wrapped);
462 
463 	git_vector_free(&t->heads);
464 	git_vector_foreach(refs, i, p)
465 		git_pkt_free(p);
466 
467 	git_vector_free(refs);
468 	git__free((char *)t->proxy.url);
469 
470 	git_strarray_dispose(&t->custom_headers);
471 
472 	git__free(t);
473 }
474 
ref_name_cmp(const void * a,const void * b)475 static int ref_name_cmp(const void *a, const void *b)
476 {
477 	const git_pkt_ref *ref_a = a, *ref_b = b;
478 
479 	return strcmp(ref_a->head.name, ref_b->head.name);
480 }
481 
git_transport_smart_certificate_check(git_transport * transport,git_cert * cert,int valid,const char * hostname)482 int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
483 {
484 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
485 
486 	GIT_ASSERT_ARG(transport);
487 	GIT_ASSERT_ARG(cert);
488 	GIT_ASSERT_ARG(hostname);
489 
490 	if (!t->certificate_check_cb)
491 		return GIT_PASSTHROUGH;
492 
493 	return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload);
494 }
495 
git_transport_smart_credentials(git_credential ** out,git_transport * transport,const char * user,int methods)496 int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods)
497 {
498 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
499 
500 	GIT_ASSERT_ARG(out);
501 	GIT_ASSERT_ARG(transport);
502 
503 	if (!t->cred_acquire_cb)
504 		return GIT_PASSTHROUGH;
505 
506 	return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload);
507 }
508 
git_transport_smart_proxy_options(git_proxy_options * out,git_transport * transport)509 int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport)
510 {
511 	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
512 	return git_proxy_options_dup(out, &t->proxy);
513 }
514 
git_transport_smart(git_transport ** out,git_remote * owner,void * param)515 int git_transport_smart(git_transport **out, git_remote *owner, void *param)
516 {
517 	transport_smart *t;
518 	git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
519 
520 	if (!param)
521 		return -1;
522 
523 	t = git__calloc(1, sizeof(transport_smart));
524 	GIT_ERROR_CHECK_ALLOC(t);
525 
526 	t->parent.version = GIT_TRANSPORT_VERSION;
527 	t->parent.set_callbacks = git_smart__set_callbacks;
528 	t->parent.set_custom_headers = git_smart__set_custom_headers;
529 	t->parent.connect = git_smart__connect;
530 	t->parent.close = git_smart__close;
531 	t->parent.free = git_smart__free;
532 	t->parent.negotiate_fetch = git_smart__negotiate_fetch;
533 	t->parent.download_pack = git_smart__download_pack;
534 	t->parent.push = git_smart__push;
535 	t->parent.ls = git_smart__ls;
536 	t->parent.is_connected = git_smart__is_connected;
537 	t->parent.read_flags = git_smart__read_flags;
538 	t->parent.cancel = git_smart__cancel;
539 
540 	t->owner = owner;
541 	t->rpc = definition->rpc;
542 
543 	if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
544 		git__free(t);
545 		return -1;
546 	}
547 
548 	if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
549 		git__free(t);
550 		return -1;
551 	}
552 
553 	if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
554 		git__free(t);
555 		return -1;
556 	}
557 
558 	*out = (git_transport *) t;
559 	return 0;
560 }
561