]>
Commit | Line | Data |
---|---|---|
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 | 21 | static const char prefix_ssh[] = "ssh://"; |
297758dc BM |
22 | static const char cmd_uploadpack[] = "git-upload-pack"; |
23 | static const char cmd_receivepack[] = "git-receive-pack"; | |
24 | ||
25 | typedef 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 | ||
35 | typedef 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 |
42 | static 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 | 55 | static 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 | 85 | static 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 |
102 | cleanup: |
103 | git_buf_free(&request); | |
104 | return error; | |
105 | } | |
106 | ||
8ae55d94 BM |
107 | static 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 |
131 | static 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 | 160 | static 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 |
190 | static 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 | 220 | static 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 |
252 | static 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 | ||
289 | shutdown: | |
290 | libssh2_agent_disconnect(agent); | |
291 | libssh2_agent_free(agent); | |
292 | ||
293 | return rc; | |
294 | } | |
295 | ||
c0cef9e0 BM |
296 | static 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 | 363 | static 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 | 395 | static 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 | 483 | on_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 | 502 | static 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 | 513 | static 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 | 529 | static 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 | 540 | static 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 | 556 | static 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 | 582 | static 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 | 593 | static 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 |
603 | int 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 | } |