]>
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 | ||
8 | #include "git2.h" | |
9 | #include "buffer.h" | |
10 | #include "netops.h" | |
f7158cd7 | 11 | #include "smart.h" |
297758dc BM |
12 | |
13 | #include <libssh2.h> | |
14 | ||
15 | #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) | |
16 | ||
d04c3840 | 17 | static const char prefix_ssh[] = "ssh://"; |
297758dc BM |
18 | static const char prefix_git[] = "git@"; |
19 | static const char cmd_uploadpack[] = "git-upload-pack"; | |
20 | static const char cmd_receivepack[] = "git-receive-pack"; | |
21 | ||
22 | typedef struct { | |
23 | git_smart_subtransport_stream parent; | |
24 | gitno_socket socket; | |
f7158cd7 BM |
25 | LIBSSH2_SESSION *session; |
26 | LIBSSH2_CHANNEL *channel; | |
297758dc BM |
27 | const char *cmd; |
28 | char *url; | |
29 | unsigned sent_command : 1; | |
30 | } ssh_stream; | |
31 | ||
32 | typedef struct { | |
33 | git_smart_subtransport parent; | |
f7158cd7 | 34 | transport_smart *owner; |
8ae55d94 | 35 | ssh_stream *current_stream; |
f7158cd7 | 36 | git_cred *cred; |
297758dc BM |
37 | } ssh_subtransport; |
38 | ||
39 | /* | |
40 | * Create a git protocol request. | |
41 | * | |
d04c3840 | 42 | * For example: 0035git-upload-pack /libgit2/libgit2\0 |
297758dc BM |
43 | */ |
44 | static int gen_proto(git_buf *request, const char *cmd, const char *url) | |
45 | { | |
46 | char *delim, *repo; | |
297758dc BM |
47 | size_t len; |
48 | ||
f7158cd7 | 49 | delim = strchr(url, ':'); |
297758dc BM |
50 | if (delim == NULL) { |
51 | giterr_set(GITERR_NET, "Malformed URL"); | |
52 | return -1; | |
53 | } | |
54 | ||
f7158cd7 BM |
55 | repo = delim+1; |
56 | len = strlen(cmd) + 1 + 1 + strlen(repo) + 1; | |
297758dc BM |
57 | |
58 | git_buf_grow(request, len); | |
f7158cd7 | 59 | git_buf_printf(request, "%s '%s'", cmd, repo); |
297758dc BM |
60 | git_buf_putc(request, '\0'); |
61 | ||
62 | if (git_buf_oom(request)) | |
63 | return -1; | |
64 | ||
65 | return 0; | |
66 | } | |
67 | ||
8ae55d94 | 68 | static int send_command(ssh_stream *s) |
297758dc BM |
69 | { |
70 | int error; | |
71 | git_buf request = GIT_BUF_INIT; | |
72 | ||
73 | error = gen_proto(&request, s->cmd, s->url); | |
74 | if (error < 0) | |
75 | goto cleanup; | |
76 | ||
f7158cd7 BM |
77 | error = libssh2_channel_process_startup( |
78 | s->channel, | |
79 | "exec", | |
80 | (uint32_t)sizeof("exec") - 1, | |
81 | request.ptr, | |
82 | request.size | |
83 | ); | |
84 | ||
85 | if (0 != error) | |
86 | goto cleanup; | |
297758dc | 87 | |
f7158cd7 | 88 | s->sent_command = 1; |
297758dc BM |
89 | |
90 | cleanup: | |
91 | git_buf_free(&request); | |
92 | return error; | |
93 | } | |
94 | ||
8ae55d94 BM |
95 | static int ssh_stream_read( |
96 | git_smart_subtransport_stream *stream, | |
97 | char *buffer, | |
98 | size_t buf_size, | |
99 | size_t *bytes_read) | |
297758dc | 100 | { |
8ae55d94 | 101 | ssh_stream *s = (ssh_stream *)stream; |
297758dc BM |
102 | |
103 | *bytes_read = 0; | |
104 | ||
105 | if (!s->sent_command && send_command(s) < 0) | |
106 | return -1; | |
107 | ||
f7158cd7 | 108 | int rc = libssh2_channel_read(s->channel, buffer, buf_size); |
297758dc | 109 | |
f7158cd7 | 110 | if (rc < 0) |
297758dc BM |
111 | return -1; |
112 | ||
f7158cd7 | 113 | *bytes_read = rc; |
297758dc BM |
114 | |
115 | return 0; | |
116 | } | |
117 | ||
8ae55d94 BM |
118 | static int ssh_stream_write( |
119 | git_smart_subtransport_stream *stream, | |
120 | const char *buffer, | |
121 | size_t len) | |
297758dc | 122 | { |
8ae55d94 | 123 | ssh_stream *s = (ssh_stream *)stream; |
297758dc BM |
124 | |
125 | if (!s->sent_command && send_command(s) < 0) | |
126 | return -1; | |
127 | ||
f7158cd7 BM |
128 | int rc = libssh2_channel_write(s->channel, buffer, len); |
129 | if (rc < 0) { | |
130 | return -1; | |
131 | } | |
132 | ||
133 | return rc; | |
297758dc BM |
134 | } |
135 | ||
8ae55d94 | 136 | static void ssh_stream_free(git_smart_subtransport_stream *stream) |
297758dc | 137 | { |
8ae55d94 | 138 | ssh_stream *s = (ssh_stream *)stream; |
297758dc BM |
139 | ssh_subtransport *t = OWNING_SUBTRANSPORT(s); |
140 | int ret; | |
141 | ||
142 | GIT_UNUSED(ret); | |
143 | ||
144 | t->current_stream = NULL; | |
145 | ||
146 | if (s->socket.socket) { | |
147 | ret = gitno_close(&s->socket); | |
148 | assert(!ret); | |
149 | } | |
150 | ||
151 | git__free(s->url); | |
152 | git__free(s); | |
153 | } | |
154 | ||
8ae55d94 BM |
155 | static int ssh_stream_alloc( |
156 | ssh_subtransport *t, | |
157 | const char *url, | |
158 | const char *cmd, | |
159 | git_smart_subtransport_stream **stream) | |
297758dc | 160 | { |
8ae55d94 | 161 | ssh_stream *s; |
297758dc BM |
162 | |
163 | if (!stream) | |
164 | return -1; | |
165 | ||
8ae55d94 | 166 | s = git__calloc(sizeof(ssh_stream), 1); |
297758dc BM |
167 | GITERR_CHECK_ALLOC(s); |
168 | ||
169 | s->parent.subtransport = &t->parent; | |
8ae55d94 BM |
170 | s->parent.read = ssh_stream_read; |
171 | s->parent.write = ssh_stream_write; | |
172 | s->parent.free = ssh_stream_free; | |
297758dc BM |
173 | |
174 | s->cmd = cmd; | |
175 | s->url = git__strdup(url); | |
176 | ||
177 | if (!s->url) { | |
178 | git__free(s); | |
179 | return -1; | |
180 | } | |
181 | ||
182 | *stream = &s->parent; | |
183 | return 0; | |
184 | } | |
185 | ||
d04c3840 BM |
186 | /* Temp */ |
187 | static int gitssh_extract_url_parts( | |
188 | char **host, | |
189 | char **username, | |
190 | char **path, | |
191 | const char *url) | |
192 | { | |
193 | char *colon, *at; | |
194 | const char *start; | |
195 | ||
196 | colon = strchr(url, ':'); | |
197 | at = strchr(url, '@'); | |
198 | ||
199 | if (colon == NULL) { | |
200 | giterr_set(GITERR_NET, "Malformed URL: missing :"); | |
201 | return -1; | |
202 | } | |
203 | ||
204 | start = url; | |
205 | if (at) { | |
206 | start = at+1; | |
207 | *username = git__substrdup(url, at - url); | |
208 | } else { | |
209 | *username = "git"; | |
210 | } | |
211 | ||
212 | *host = git__substrdup(start, colon - start); | |
213 | *path = colon+1; | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
297758dc | 218 | static int _git_uploadpack_ls( |
8ae55d94 BM |
219 | ssh_subtransport *t, |
220 | const char *url, | |
221 | git_smart_subtransport_stream **stream) | |
297758dc BM |
222 | { |
223 | char *host, *port, *user=NULL, *pass=NULL; | |
8ae55d94 | 224 | ssh_stream *s; |
297758dc BM |
225 | |
226 | *stream = NULL; | |
227 | ||
d04c3840 BM |
228 | if (!git__prefixcmp(url, prefix_ssh)) |
229 | url += strlen(prefix_ssh); | |
297758dc | 230 | |
8ae55d94 | 231 | if (ssh_stream_alloc(t, url, cmd_uploadpack, stream) < 0) |
297758dc BM |
232 | return -1; |
233 | ||
8ae55d94 | 234 | s = (ssh_stream *)*stream; |
297758dc BM |
235 | |
236 | if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0) | |
237 | goto on_error; | |
238 | ||
239 | if (gitno_connect(&s->socket, host, port, 0) < 0) | |
240 | goto on_error; | |
241 | ||
242 | t->current_stream = s; | |
243 | git__free(host); | |
244 | git__free(port); | |
245 | git__free(user); | |
246 | git__free(pass); | |
247 | return 0; | |
248 | ||
249 | on_error: | |
250 | if (*stream) | |
8ae55d94 | 251 | ssh_stream_free(*stream); |
297758dc BM |
252 | |
253 | git__free(host); | |
254 | git__free(port); | |
255 | return -1; | |
256 | } | |
257 | ||
258 | static int _git_uploadpack( | |
8ae55d94 BM |
259 | ssh_subtransport *t, |
260 | const char *url, | |
261 | git_smart_subtransport_stream **stream) | |
297758dc BM |
262 | { |
263 | GIT_UNUSED(url); | |
264 | ||
265 | if (t->current_stream) { | |
266 | *stream = &t->current_stream->parent; | |
267 | return 0; | |
268 | } | |
269 | ||
270 | giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); | |
271 | return -1; | |
272 | } | |
273 | ||
274 | static int _git_receivepack_ls( | |
8ae55d94 BM |
275 | ssh_subtransport *t, |
276 | const char *url, | |
277 | git_smart_subtransport_stream **stream) | |
297758dc | 278 | { |
d04c3840 | 279 | char *host, *path, *user=NULL; |
8ae55d94 | 280 | ssh_stream *s; |
297758dc BM |
281 | |
282 | *stream = NULL; | |
8ae55d94 | 283 | if (ssh_stream_alloc(t, url, cmd_receivepack, stream) < 0) |
297758dc BM |
284 | return -1; |
285 | ||
8ae55d94 | 286 | s = (ssh_stream *)*stream; |
297758dc | 287 | |
d04c3840 | 288 | if (gitssh_extract_url_parts(&host, &user, &path, url) < 0) |
297758dc BM |
289 | goto on_error; |
290 | ||
d04c3840 | 291 | if (gitno_connect(&s->socket, host, "22", 0) < 0) |
297758dc BM |
292 | goto on_error; |
293 | ||
f7158cd7 BM |
294 | if (t->owner->cred_acquire_cb(&t->cred, |
295 | t->owner->url, | |
296 | user, | |
297 | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, | |
298 | t->owner->cred_acquire_payload) < 0) | |
299 | return -1; | |
300 | ||
301 | assert(t->cred); | |
302 | ||
303 | git_cred_ssh_keyfile_passphrase *cred = (git_cred_ssh_keyfile_passphrase *)t->cred; | |
304 | ||
d04c3840 BM |
305 | LIBSSH2_SESSION* session = libssh2_session_init(); |
306 | if (!session) | |
307 | goto on_error; | |
308 | ||
309 | int rc = 0; | |
310 | do { | |
311 | rc = libssh2_session_startup(session, s->socket.socket); | |
312 | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | |
313 | ||
314 | if (0 != rc) { | |
315 | goto on_error; | |
316 | } | |
317 | ||
318 | libssh2_trace(session, 0x1FF); | |
319 | libssh2_session_set_blocking(session, 1); | |
320 | ||
321 | do { | |
322 | rc = libssh2_userauth_publickey_fromfile_ex( | |
323 | session, | |
324 | user, | |
325 | strlen(user), | |
f7158cd7 BM |
326 | cred->publickey, |
327 | cred->privatekey, | |
328 | cred->passphrase | |
d04c3840 BM |
329 | ); |
330 | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | |
331 | ||
332 | if (0 != rc) { | |
333 | goto on_error; | |
334 | } | |
335 | ||
336 | LIBSSH2_CHANNEL* channel = NULL; | |
337 | do { | |
338 | channel = libssh2_channel_open_session(session); | |
339 | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | |
340 | ||
341 | if (!channel) { | |
342 | goto on_error; | |
343 | } | |
344 | ||
345 | libssh2_channel_set_blocking(channel, 1); | |
346 | ||
f7158cd7 BM |
347 | s->session = session; |
348 | s->channel = channel; | |
349 | ||
297758dc BM |
350 | t->current_stream = s; |
351 | git__free(host); | |
297758dc BM |
352 | return 0; |
353 | ||
354 | on_error: | |
355 | if (*stream) | |
8ae55d94 | 356 | ssh_stream_free(*stream); |
297758dc BM |
357 | |
358 | git__free(host); | |
d04c3840 | 359 | git__free(path); |
297758dc BM |
360 | return -1; |
361 | } | |
362 | ||
363 | static int _git_receivepack( | |
8ae55d94 BM |
364 | ssh_subtransport *t, |
365 | const char *url, | |
366 | git_smart_subtransport_stream **stream) | |
297758dc BM |
367 | { |
368 | GIT_UNUSED(url); | |
369 | ||
370 | if (t->current_stream) { | |
371 | *stream = &t->current_stream->parent; | |
372 | return 0; | |
373 | } | |
374 | ||
375 | giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); | |
376 | return -1; | |
377 | } | |
378 | ||
379 | static int _git_action( | |
8ae55d94 BM |
380 | git_smart_subtransport_stream **stream, |
381 | git_smart_subtransport *subtransport, | |
382 | const char *url, | |
383 | git_smart_service_t action) | |
297758dc BM |
384 | { |
385 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
386 | ||
387 | switch (action) { | |
388 | case GIT_SERVICE_UPLOADPACK_LS: | |
389 | return _git_uploadpack_ls(t, url, stream); | |
390 | ||
391 | case GIT_SERVICE_UPLOADPACK: | |
392 | return _git_uploadpack(t, url, stream); | |
393 | ||
394 | case GIT_SERVICE_RECEIVEPACK_LS: | |
395 | return _git_receivepack_ls(t, url, stream); | |
396 | ||
397 | case GIT_SERVICE_RECEIVEPACK: | |
398 | return _git_receivepack(t, url, stream); | |
399 | } | |
400 | ||
401 | *stream = NULL; | |
402 | return -1; | |
403 | } | |
404 | ||
405 | static int _git_close(git_smart_subtransport *subtransport) | |
406 | { | |
407 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
408 | ||
409 | assert(!t->current_stream); | |
410 | ||
411 | GIT_UNUSED(t); | |
412 | ||
413 | return 0; | |
414 | } | |
415 | ||
416 | static void _git_free(git_smart_subtransport *subtransport) | |
417 | { | |
418 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
419 | ||
420 | assert(!t->current_stream); | |
421 | ||
422 | git__free(t); | |
423 | } | |
424 | ||
d04c3840 | 425 | int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner) |
297758dc BM |
426 | { |
427 | ssh_subtransport *t; | |
428 | ||
429 | if (!out) | |
430 | return -1; | |
431 | ||
432 | t = git__calloc(sizeof(ssh_subtransport), 1); | |
433 | GITERR_CHECK_ALLOC(t); | |
434 | ||
f7158cd7 | 435 | t->owner = (transport_smart *)owner; |
297758dc BM |
436 | t->parent.action = _git_action; |
437 | t->parent.close = _git_close; | |
438 | t->parent.free = _git_free; | |
439 | ||
440 | *out = (git_smart_subtransport *) t; | |
441 | return 0; | |
442 | } |