]> git.proxmox.com Git - libgit2.git/blame - src/transports/ssh.c
Push working over ssh
[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
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 17static const char prefix_ssh[] = "ssh://";
297758dc
BM
18static const char prefix_git[] = "git@";
19static const char cmd_uploadpack[] = "git-upload-pack";
20static const char cmd_receivepack[] = "git-receive-pack";
21
22typedef 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
32typedef 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 */
44static 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 68static 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
90cleanup:
91 git_buf_free(&request);
92 return error;
93}
94
8ae55d94
BM
95static 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
118static 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 136static 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
155static 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 */
187static 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 218static 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
249on_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
258static 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
274static 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
354on_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
363static 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
379static 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
405static 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
416static 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 425int 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}