]> git.proxmox.com Git - libgit2.git/blame - src/transports/ssh.c
Added ssh transport file
[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"
11
12#include <libssh2.h>
13
14#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
15
16static const char prefix_git_ssh[] = "git+ssh://";
17static const char prefix_ssh_git[] = "ssh+git://";
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;
25 const char *cmd;
26 char *url;
27 unsigned sent_command : 1;
28} ssh_stream;
29
30typedef struct {
31 git_smart_subtransport parent;
32 git_transport *owner;
33 git_stream *current_stream;
34} ssh_subtransport;
35
36/*
37 * Create a git protocol request.
38 *
39 * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
40 */
41static int gen_proto(git_buf *request, const char *cmd, const char *url)
42{
43 char *delim, *repo;
44 char host[] = "host=";
45 size_t len;
46
47 delim = strchr(url, '/');
48 if (delim == NULL) {
49 giterr_set(GITERR_NET, "Malformed URL");
50 return -1;
51 }
52
53 repo = delim;
54
55 delim = strchr(url, ':');
56 if (delim == NULL)
57 delim = strchr(url, '/');
58
59 len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
60
61 git_buf_grow(request, len);
62 git_buf_printf(request, "%04x%s %s%c%s",
63 (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host);
64 git_buf_put(request, url, delim - url);
65 git_buf_putc(request, '\0');
66
67 if (git_buf_oom(request))
68 return -1;
69
70 return 0;
71}
72
73static int send_command(git_stream *s)
74{
75 int error;
76 git_buf request = GIT_BUF_INIT;
77
78 error = gen_proto(&request, s->cmd, s->url);
79 if (error < 0)
80 goto cleanup;
81
82 /* It looks like negative values are errors here, and positive values
83 * are the number of bytes sent. */
84 error = gitno_send(&s->socket, request.ptr, request.size, 0);
85
86 if (error >= 0)
87 s->sent_command = 1;
88
89cleanup:
90 git_buf_free(&request);
91 return error;
92}
93
94static int git_stream_read(
95 git_smart_subtransport_stream *stream,
96 char *buffer,
97 size_t buf_size,
98 size_t *bytes_read)
99{
100 git_stream *s = (git_stream *)stream;
101 gitno_buffer buf;
102
103 *bytes_read = 0;
104
105 if (!s->sent_command && send_command(s) < 0)
106 return -1;
107
108 gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
109
110 if (gitno_recv(&buf) < 0)
111 return -1;
112
113 *bytes_read = buf.offset;
114
115 return 0;
116}
117
118static int git_stream_write(
119 git_smart_subtransport_stream *stream,
120 const char *buffer,
121 size_t len)
122{
123 git_stream *s = (git_stream *)stream;
124
125 if (!s->sent_command && send_command(s) < 0)
126 return -1;
127
128 return gitno_send(&s->socket, buffer, len, 0);
129}
130
131static void git_stream_free(git_smart_subtransport_stream *stream)
132{
133 git_stream *s = (git_stream *)stream;
134 ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
135 int ret;
136
137 GIT_UNUSED(ret);
138
139 t->current_stream = NULL;
140
141 if (s->socket.socket) {
142 ret = gitno_close(&s->socket);
143 assert(!ret);
144 }
145
146 git__free(s->url);
147 git__free(s);
148}
149
150static int git_stream_alloc(
151 ssh_subtransport *t,
152 const char *url,
153 const char *cmd,
154 git_smart_subtransport_stream **stream)
155{
156 git_stream *s;
157
158 if (!stream)
159 return -1;
160
161 s = git__calloc(sizeof(git_stream), 1);
162 GITERR_CHECK_ALLOC(s);
163
164 s->parent.subtransport = &t->parent;
165 s->parent.read = git_stream_read;
166 s->parent.write = git_stream_write;
167 s->parent.free = git_stream_free;
168
169 s->cmd = cmd;
170 s->url = git__strdup(url);
171
172 if (!s->url) {
173 git__free(s);
174 return -1;
175 }
176
177 *stream = &s->parent;
178 return 0;
179}
180
181static int _git_uploadpack_ls(
182 ssh_subtransport *t,
183 const char *url,
184 git_smart_subtransport_stream **stream)
185{
186 char *host, *port, *user=NULL, *pass=NULL;
187 git_stream *s;
188
189 *stream = NULL;
190
191 if (!git__prefixcmp(url, prefix_git))
192 url += strlen(prefix_git);
193
194 if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
195 return -1;
196
197 s = (git_stream *)*stream;
198
199 if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
200 goto on_error;
201
202 if (gitno_connect(&s->socket, host, port, 0) < 0)
203 goto on_error;
204
205 t->current_stream = s;
206 git__free(host);
207 git__free(port);
208 git__free(user);
209 git__free(pass);
210 return 0;
211
212on_error:
213 if (*stream)
214 git_stream_free(*stream);
215
216 git__free(host);
217 git__free(port);
218 return -1;
219}
220
221static int _git_uploadpack(
222 ssh_subtransport *t,
223 const char *url,
224 git_smart_subtransport_stream **stream)
225{
226 GIT_UNUSED(url);
227
228 if (t->current_stream) {
229 *stream = &t->current_stream->parent;
230 return 0;
231 }
232
233 giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
234 return -1;
235}
236
237static int _git_receivepack_ls(
238 ssh_subtransport *t,
239 const char *url,
240 git_smart_subtransport_stream **stream)
241{
242 char *host, *port, *user=NULL, *pass=NULL;
243 git_stream *s;
244
245 *stream = NULL;
246
247 if (!git__prefixcmp(url, prefix_git))
248 url += strlen(prefix_git);
249
250 if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
251 return -1;
252
253 s = (git_stream *)*stream;
254
255 if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
256 goto on_error;
257
258 if (gitno_connect(&s->socket, host, port, 0) < 0)
259 goto on_error;
260
261 t->current_stream = s;
262 git__free(host);
263 git__free(port);
264 git__free(user);
265 git__free(pass);
266 return 0;
267
268on_error:
269 if (*stream)
270 git_stream_free(*stream);
271
272 git__free(host);
273 git__free(port);
274 return -1;
275}
276
277static int _git_receivepack(
278 ssh_subtransport *t,
279 const char *url,
280 git_smart_subtransport_stream **stream)
281{
282 GIT_UNUSED(url);
283
284 if (t->current_stream) {
285 *stream = &t->current_stream->parent;
286 return 0;
287 }
288
289 giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
290 return -1;
291}
292
293static int _git_action(
294 git_smart_subtransport_stream **stream,
295 git_smart_subtransport *subtransport,
296 const char *url,
297 git_smart_service_t action)
298{
299 ssh_subtransport *t = (ssh_subtransport *) subtransport;
300
301 switch (action) {
302 case GIT_SERVICE_UPLOADPACK_LS:
303 return _git_uploadpack_ls(t, url, stream);
304
305 case GIT_SERVICE_UPLOADPACK:
306 return _git_uploadpack(t, url, stream);
307
308 case GIT_SERVICE_RECEIVEPACK_LS:
309 return _git_receivepack_ls(t, url, stream);
310
311 case GIT_SERVICE_RECEIVEPACK:
312 return _git_receivepack(t, url, stream);
313 }
314
315 *stream = NULL;
316 return -1;
317}
318
319static int _git_close(git_smart_subtransport *subtransport)
320{
321 ssh_subtransport *t = (ssh_subtransport *) subtransport;
322
323 assert(!t->current_stream);
324
325 GIT_UNUSED(t);
326
327 return 0;
328}
329
330static void _git_free(git_smart_subtransport *subtransport)
331{
332 ssh_subtransport *t = (ssh_subtransport *) subtransport;
333
334 assert(!t->current_stream);
335
336 git__free(t);
337}
338
339int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
340{
341 ssh_subtransport *t;
342
343 if (!out)
344 return -1;
345
346 t = git__calloc(sizeof(ssh_subtransport), 1);
347 GITERR_CHECK_ALLOC(t);
348
349 t->owner = owner;
350 t->parent.action = _git_action;
351 t->parent.close = _git_close;
352 t->parent.free = _git_free;
353
354 *out = (git_smart_subtransport *) t;
355 return 0;
356}