]> git.proxmox.com Git - libgit2.git/blame - src/transports/ssh.c
Merge remote-tracking branch 'origin/cmn/update-zlib'
[libgit2.git] / src / transports / ssh.c
CommitLineData
297758dc
BM
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
ae241ae1
JG
8#ifdef GIT_SSH
9#include <libssh2.h>
10#endif
11
297758dc
BM
12#include "git2.h"
13#include "buffer.h"
14#include "netops.h"
f7158cd7 15#include "smart.h"
297758dc 16
a3c062db
RB
17#ifdef GIT_SSH
18
297758dc
BM
19#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
20
d04c3840 21static const char prefix_ssh[] = "ssh://";
297758dc
BM
22static const char cmd_uploadpack[] = "git-upload-pack";
23static const char cmd_receivepack[] = "git-receive-pack";
24
25typedef struct {
26 git_smart_subtransport_stream parent;
27 gitno_socket socket;
f7158cd7
BM
28 LIBSSH2_SESSION *session;
29 LIBSSH2_CHANNEL *channel;
297758dc
BM
30 const char *cmd;
31 char *url;
32 unsigned sent_command : 1;
33} ssh_stream;
34
35typedef struct {
36 git_smart_subtransport parent;
f7158cd7 37 transport_smart *owner;
8ae55d94 38 ssh_stream *current_stream;
f7158cd7 39 git_cred *cred;
297758dc
BM
40} ssh_subtransport;
41
b622aabe
ES
42static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
43{
44 char *ssherr;
45 libssh2_session_last_error(session, &ssherr, NULL, 0);
46
47 giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr);
48}
49
297758dc
BM
50/*
51 * Create a git protocol request.
52 *
58ba0a4e 53 * For example: git-upload-pack '/libgit2/libgit2'
297758dc 54 */
7261d983 55static int gen_proto(git_buf *request, const char *cmd, const char *url)
297758dc 56{
7261d983 57 char *repo;
300f4412 58 int len;
a3c062db 59
7261d983
BM
60 if (!git__prefixcmp(url, prefix_ssh)) {
61 url = url + strlen(prefix_ssh);
62 repo = strchr(url, '/');
63 } else {
64 repo = strchr(url, ':');
b345026b 65 if (repo) repo++;
7261d983 66 }
a3c062db 67
7261d983 68 if (!repo) {
c2de6b1a 69 giterr_set(GITERR_NET, "Malformed git protocol URL");
7261d983
BM
70 return -1;
71 }
a3c062db 72
300f4412 73 len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
a3c062db 74
297758dc 75 git_buf_grow(request, len);
f7158cd7 76 git_buf_printf(request, "%s '%s'", cmd, repo);
297758dc 77 git_buf_putc(request, '\0');
a3c062db 78
297758dc
BM
79 if (git_buf_oom(request))
80 return -1;
a3c062db 81
297758dc
BM
82 return 0;
83}
84
8ae55d94 85static int send_command(ssh_stream *s)
297758dc
BM
86{
87 int error;
88 git_buf request = GIT_BUF_INIT;
a3c062db 89
7261d983 90 error = gen_proto(&request, s->cmd, s->url);
297758dc
BM
91 if (error < 0)
92 goto cleanup;
a3c062db 93
c2de6b1a 94 error = libssh2_channel_exec(s->channel, request.ptr);
b622aabe
ES
95 if (error < LIBSSH2_ERROR_NONE) {
96 ssh_error(s->session, "SSH could not execute request");
f7158cd7 97 goto cleanup;
c2de6b1a 98 }
a3c062db 99
f7158cd7 100 s->sent_command = 1;
a3c062db 101
297758dc
BM
102cleanup:
103 git_buf_free(&request);
104 return error;
105}
106
8ae55d94
BM
107static int ssh_stream_read(
108 git_smart_subtransport_stream *stream,
109 char *buffer,
110 size_t buf_size,
111 size_t *bytes_read)
297758dc 112{
c2de6b1a 113 int rc;
8ae55d94 114 ssh_stream *s = (ssh_stream *)stream;
a3c062db 115
297758dc 116 *bytes_read = 0;
a3c062db 117
297758dc
BM
118 if (!s->sent_command && send_command(s) < 0)
119 return -1;
a3c062db 120
b622aabe
ES
121 if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
122 ssh_error(s->session, "SSH could not read data");;
297758dc 123 return -1;
c2de6b1a 124 }
a3c062db 125
f7158cd7 126 *bytes_read = rc;
a3c062db 127
297758dc
BM
128 return 0;
129}
130
8ae55d94
BM
131static int ssh_stream_write(
132 git_smart_subtransport_stream *stream,
133 const char *buffer,
134 size_t len)
297758dc 135{
8ae55d94 136 ssh_stream *s = (ssh_stream *)stream;
0963716b
CMN
137 size_t off = 0;
138 ssize_t ret = 0;
a3c062db 139
297758dc
BM
140 if (!s->sent_command && send_command(s) < 0)
141 return -1;
a3c062db 142
0963716b
CMN
143 do {
144 ret = libssh2_channel_write(s->channel, buffer + off, len - off);
145 if (ret < 0)
146 break;
147
148 off += ret;
149
150 } while (off < len);
151
152 if (ret < 0) {
b622aabe 153 ssh_error(s->session, "SSH could not write data");
f7158cd7
BM
154 return -1;
155 }
a3c062db 156
c2de6b1a 157 return 0;
297758dc
BM
158}
159
8ae55d94 160static void ssh_stream_free(git_smart_subtransport_stream *stream)
297758dc 161{
8ae55d94 162 ssh_stream *s = (ssh_stream *)stream;
297758dc
BM
163 ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
164 int ret;
a3c062db 165
297758dc 166 GIT_UNUSED(ret);
a3c062db 167
297758dc 168 t->current_stream = NULL;
a3c062db 169
22595b84
BM
170 if (s->channel) {
171 libssh2_channel_close(s->channel);
68bc49a1
RB
172 libssh2_channel_free(s->channel);
173 s->channel = NULL;
22595b84 174 }
a3c062db 175
22595b84 176 if (s->session) {
c2de6b1a
RB
177 libssh2_session_free(s->session);
178 s->session = NULL;
22595b84 179 }
a3c062db 180
297758dc 181 if (s->socket.socket) {
c2de6b1a
RB
182 (void)gitno_close(&s->socket);
183 /* can't do anything here with error return value */
297758dc 184 }
a3c062db 185
297758dc
BM
186 git__free(s->url);
187 git__free(s);
188}
189
8ae55d94
BM
190static int ssh_stream_alloc(
191 ssh_subtransport *t,
192 const char *url,
193 const char *cmd,
194 git_smart_subtransport_stream **stream)
297758dc 195{
8ae55d94 196 ssh_stream *s;
a3c062db 197
c2de6b1a 198 assert(stream);
a3c062db 199
8ae55d94 200 s = git__calloc(sizeof(ssh_stream), 1);
297758dc 201 GITERR_CHECK_ALLOC(s);
a3c062db 202
297758dc 203 s->parent.subtransport = &t->parent;
8ae55d94
BM
204 s->parent.read = ssh_stream_read;
205 s->parent.write = ssh_stream_write;
206 s->parent.free = ssh_stream_free;
a3c062db 207
297758dc 208 s->cmd = cmd;
a3c062db 209
c2de6b1a 210 s->url = git__strdup(url);
297758dc
BM
211 if (!s->url) {
212 git__free(s);
213 return -1;
214 }
a3c062db 215
297758dc
BM
216 *stream = &s->parent;
217 return 0;
218}
219
7261d983 220static int git_ssh_extract_url_parts(
d04c3840
BM
221 char **host,
222 char **username,
d04c3840
BM
223 const char *url)
224{
225 char *colon, *at;
226 const char *start;
a3c062db 227
68bc49a1 228 colon = strchr(url, ':');
a3c062db 229
a3c062db 230
7261d983 231 at = strchr(url, '@');
d04c3840 232 if (at) {
eec4dcc3 233 start = at + 1;
d04c3840 234 *username = git__substrdup(url, at - url);
7affc2f7 235 GITERR_CHECK_ALLOC(*username);
d04c3840 236 } else {
c87bf86c 237 start = url;
7affc2f7 238 *username = NULL;
d04c3840 239 }
a3c062db 240
048f837b
BS
241 if (colon == NULL || (colon < start)) {
242 giterr_set(GITERR_NET, "Malformed URL");
243 return -1;
244 }
245
d04c3840 246 *host = git__substrdup(start, colon - start);
c2de6b1a 247 GITERR_CHECK_ALLOC(*host);
a3c062db 248
d04c3840
BM
249 return 0;
250}
251
ee7040fd
AG
252static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) {
253 int rc = LIBSSH2_ERROR_NONE;
254
255 struct libssh2_agent_publickey *curr, *prev = NULL;
256
257 LIBSSH2_AGENT *agent = libssh2_agent_init(session);
258
259 if (agent == NULL)
260 return -1;
261
262 rc = libssh2_agent_connect(agent);
263
264 if (rc != LIBSSH2_ERROR_NONE)
265 goto shutdown;
266
267 rc = libssh2_agent_list_identities(agent);
268
269 if (rc != LIBSSH2_ERROR_NONE)
270 goto shutdown;
271
272 while (1) {
273 rc = libssh2_agent_get_identity(agent, &curr, prev);
274
275 if (rc < 0)
276 goto shutdown;
277
278 if (rc == 1)
279 goto shutdown;
280
281 rc = libssh2_agent_userauth(agent, c->username, curr);
282
283 if (rc == 0)
284 break;
285
286 prev = curr;
287 }
288
289shutdown:
290 libssh2_agent_disconnect(agent);
291 libssh2_agent_free(agent);
292
293 return rc;
294}
295
c0cef9e0
BM
296static int _git_ssh_authenticate_session(
297 LIBSSH2_SESSION* session,
68bc49a1 298 git_cred* cred)
c0cef9e0
BM
299{
300 int rc;
c2de6b1a 301
c0cef9e0
BM
302 do {
303 switch (cred->credtype) {
c2de6b1a
RB
304 case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
305 git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
bd270b70 306 rc = libssh2_userauth_password(session, c->username, c->password);
c2de6b1a
RB
307 break;
308 }
70a8c78f
CMN
309 case GIT_CREDTYPE_SSH_KEY: {
310 git_cred_ssh_key *c = (git_cred_ssh_key *)cred;
ee7040fd
AG
311
312 if (c->privatekey)
313 rc = libssh2_userauth_publickey_fromfile(
314 session, c->username, c->publickey,
315 c->privatekey, c->passphrase);
316 else
317 rc = ssh_agent_auth(session, c);
318
c2de6b1a
RB
319 break;
320 }
70a8c78f
CMN
321 case GIT_CREDTYPE_SSH_CUSTOM: {
322 git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
7affc2f7 323
c2de6b1a 324 rc = libssh2_userauth_publickey(
7affc2f7 325 session, c->username, (const unsigned char *)c->publickey,
8ec0a552 326 c->publickey_len, c->sign_callback, &c->payload);
c2de6b1a
RB
327 break;
328 }
478408c0
JG
329 case GIT_CREDTYPE_SSH_INTERACTIVE: {
330 void **abstract = libssh2_session_abstract(session);
331 git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
332
333 /* ideally, we should be able to set this by calling
334 * libssh2_session_init_ex() instead of libssh2_session_init().
335 * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
336 * allows you to pass the `abstract` as part of the call, whereas
337 * libssh2_userauth_keyboard_interactive() does not!
338 *
339 * The only way to set the `abstract` pointer is by calling
340 * libssh2_session_abstract(), which will replace the existing
341 * pointer as is done below. This is safe for now (at time of writing),
342 * but may not be valid in future.
343 */
344 *abstract = c->payload;
345
346 rc = libssh2_userauth_keyboard_interactive(
347 session, c->username, c->prompt_callback);
348 break;
349 }
c2de6b1a
RB
350 default:
351 rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
c0cef9e0 352 }
08bf80fa 353 } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
a3c062db 354
b622aabe
ES
355 if (rc != LIBSSH2_ERROR_NONE) {
356 ssh_error(session, "Failed to authenticate SSH session");
c2de6b1a
RB
357 return -1;
358 }
359
360 return 0;
c0cef9e0
BM
361}
362
08bf80fa 363static int _git_ssh_session_create(
3eed595e 364 LIBSSH2_SESSION** session,
68bc49a1 365 gitno_socket socket)
3eed595e 366{
c2de6b1a
RB
367 int rc = 0;
368 LIBSSH2_SESSION* s;
a3c062db 369
c2de6b1a
RB
370 assert(session);
371
372 s = libssh2_session_init();
373 if (!s) {
374 giterr_set(GITERR_NET, "Failed to initialize SSH session");
08bf80fa 375 return -1;
c2de6b1a 376 }
a3c062db 377
08bf80fa
ES
378 do {
379 rc = libssh2_session_startup(s, socket.socket);
380 } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
a3c062db 381
b622aabe
ES
382 if (rc != LIBSSH2_ERROR_NONE) {
383 ssh_error(s, "Failed to start SSH session");
c2de6b1a 384 libssh2_session_free(s);
c2de6b1a 385 return -1;
68bc49a1 386 }
a3c062db 387
3eed595e 388 libssh2_session_set_blocking(s, 1);
a3c062db 389
3eed595e 390 *session = s;
a3c062db 391
3eed595e 392 return 0;
3eed595e
BM
393}
394
120b0122 395static int _git_ssh_setup_conn(
8ae55d94
BM
396 ssh_subtransport *t,
397 const char *url,
120b0122 398 const char *cmd,
c2de6b1a 399 git_smart_subtransport_stream **stream)
297758dc 400{
ac72051a 401 char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
3eed595e 402 const char *default_port="22";
bc0a6198 403 int no_callback = 0;
8ae55d94 404 ssh_stream *s;
3eed595e
BM
405 LIBSSH2_SESSION* session=NULL;
406 LIBSSH2_CHANNEL* channel=NULL;
a3c062db 407
297758dc 408 *stream = NULL;
120b0122 409 if (ssh_stream_alloc(t, url, cmd, stream) < 0)
297758dc 410 return -1;
a3c062db 411
8ae55d94 412 s = (ssh_stream *)*stream;
a3c062db 413
7261d983 414 if (!git__prefixcmp(url, prefix_ssh)) {
ac72051a 415 if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0)
c0cef9e0 416 goto on_error;
7261d983
BM
417 } else {
418 if (git_ssh_extract_url_parts(&host, &user, url) < 0)
419 goto on_error;
420 port = git__strdup(default_port);
84ac625d 421 GITERR_CHECK_ALLOC(port);
7261d983 422 }
a3c062db 423
7261d983 424 if (gitno_connect(&s->socket, host, port, 0) < 0)
297758dc 425 goto on_error;
a3c062db 426
7261d983 427 if (user && pass) {
b54ed3ef
BM
428 if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
429 goto on_error;
bc0a6198
CMN
430 } else if (!t->owner->cred_acquire_cb) {
431 no_callback = 1;
432 } else {
433 int error;
434 error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
435 GIT_CREDTYPE_USERPASS_PLAINTEXT |
436 GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
437 GIT_CREDTYPE_SSH_INTERACTIVE,
438 t->owner->cred_acquire_payload);
439
440 if (error == GIT_PASSTHROUGH)
441 no_callback = 1;
442 else if (error < 0)
f6bd0863 443 goto on_error;
bc0a6198 444 else if (!t->cred) {
b622aabe 445 giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
c2de6b1a
RB
446 goto on_error;
447 }
bc0a6198
CMN
448 }
449
450 if (no_callback) {
451 giterr_set(GITERR_SSH, "authentication required but no callback set");
367c1903 452 goto on_error;
7261d983 453 }
bc0a6198 454
f7158cd7 455 assert(t->cred);
a3c062db 456
c2de6b1a 457 if (_git_ssh_session_create(&session, s->socket) < 0)
c0cef9e0 458 goto on_error;
a3c062db 459
bd270b70 460 if (_git_ssh_authenticate_session(session, t->cred) < 0)
3eed595e 461 goto on_error;
a3c062db 462
3eed595e 463 channel = libssh2_channel_open_session(session);
367c1903 464 if (!channel) {
b622aabe 465 ssh_error(session, "Failed to open SSH channel");
367c1903
ES
466 goto on_error;
467 }
a3c062db 468
d04c3840 469 libssh2_channel_set_blocking(channel, 1);
a3c062db 470
f7158cd7
BM
471 s->session = session;
472 s->channel = channel;
a3c062db 473
297758dc
BM
474 t->current_stream = s;
475 git__free(host);
3eed595e 476 git__free(port);
ac72051a 477 git__free(path);
22011b33
BM
478 git__free(user);
479 git__free(pass);
3eed595e 480
297758dc 481 return 0;
a3c062db 482
297758dc 483on_error:
08bf80fa
ES
484 s->session = NULL;
485 s->channel = NULL;
486 t->current_stream = NULL;
487
297758dc 488 if (*stream)
8ae55d94 489 ssh_stream_free(*stream);
a3c062db 490
297758dc 491 git__free(host);
22011b33
BM
492 git__free(port);
493 git__free(user);
494 git__free(pass);
495
7621519f 496 if (session)
c2de6b1a 497 libssh2_session_free(session);
3eed595e 498
297758dc
BM
499 return -1;
500}
501
05f58131 502static int ssh_uploadpack_ls(
120b0122
BM
503 ssh_subtransport *t,
504 const char *url,
505 git_smart_subtransport_stream **stream)
506{
507 if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
508 return -1;
a3c062db 509
120b0122
BM
510 return 0;
511}
512
05f58131 513static int ssh_uploadpack(
120b0122
BM
514 ssh_subtransport *t,
515 const char *url,
516 git_smart_subtransport_stream **stream)
517{
518 GIT_UNUSED(url);
a3c062db 519
120b0122
BM
520 if (t->current_stream) {
521 *stream = &t->current_stream->parent;
522 return 0;
523 }
a3c062db 524
120b0122
BM
525 giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
526 return -1;
527}
528
05f58131 529static int ssh_receivepack_ls(
120b0122
BM
530 ssh_subtransport *t,
531 const char *url,
532 git_smart_subtransport_stream **stream)
533{
534 if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
535 return -1;
a3c062db 536
120b0122
BM
537 return 0;
538}
539
05f58131 540static int ssh_receivepack(
8ae55d94
BM
541 ssh_subtransport *t,
542 const char *url,
543 git_smart_subtransport_stream **stream)
297758dc
BM
544{
545 GIT_UNUSED(url);
a3c062db 546
297758dc
BM
547 if (t->current_stream) {
548 *stream = &t->current_stream->parent;
549 return 0;
550 }
a3c062db 551
297758dc
BM
552 giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
553 return -1;
554}
555
67a7136c 556static int _ssh_action(
8ae55d94
BM
557 git_smart_subtransport_stream **stream,
558 git_smart_subtransport *subtransport,
559 const char *url,
560 git_smart_service_t action)
297758dc
BM
561{
562 ssh_subtransport *t = (ssh_subtransport *) subtransport;
a3c062db 563
297758dc
BM
564 switch (action) {
565 case GIT_SERVICE_UPLOADPACK_LS:
05f58131 566 return ssh_uploadpack_ls(t, url, stream);
a3c062db 567
297758dc 568 case GIT_SERVICE_UPLOADPACK:
05f58131 569 return ssh_uploadpack(t, url, stream);
a3c062db 570
297758dc 571 case GIT_SERVICE_RECEIVEPACK_LS:
05f58131 572 return ssh_receivepack_ls(t, url, stream);
a3c062db 573
297758dc 574 case GIT_SERVICE_RECEIVEPACK:
05f58131 575 return ssh_receivepack(t, url, stream);
297758dc 576 }
a3c062db 577
297758dc
BM
578 *stream = NULL;
579 return -1;
580}
581
67a7136c 582static int _ssh_close(git_smart_subtransport *subtransport)
297758dc
BM
583{
584 ssh_subtransport *t = (ssh_subtransport *) subtransport;
a3c062db 585
297758dc 586 assert(!t->current_stream);
a3c062db 587
297758dc 588 GIT_UNUSED(t);
a3c062db 589
297758dc
BM
590 return 0;
591}
592
67a7136c 593static void _ssh_free(git_smart_subtransport *subtransport)
297758dc
BM
594{
595 ssh_subtransport *t = (ssh_subtransport *) subtransport;
a3c062db 596
297758dc 597 assert(!t->current_stream);
a3c062db 598
297758dc
BM
599 git__free(t);
600}
a3c062db 601#endif
297758dc 602
a3c062db
RB
603int git_smart_subtransport_ssh(
604 git_smart_subtransport **out, git_transport *owner)
297758dc 605{
a3c062db 606#ifdef GIT_SSH
297758dc 607 ssh_subtransport *t;
a3c062db
RB
608
609 assert(out);
610
297758dc
BM
611 t = git__calloc(sizeof(ssh_subtransport), 1);
612 GITERR_CHECK_ALLOC(t);
a3c062db 613
f7158cd7 614 t->owner = (transport_smart *)owner;
67a7136c
BM
615 t->parent.action = _ssh_action;
616 t->parent.close = _ssh_close;
617 t->parent.free = _ssh_free;
a3c062db 618
297758dc
BM
619 *out = (git_smart_subtransport *) t;
620 return 0;
a3c062db
RB
621#else
622 GIT_UNUSED(owner);
623
624 assert(out);
625 *out = NULL;
574b86b7 626
a3c062db
RB
627 giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
628 return -1;
574b86b7 629#endif
a3c062db 630}