1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014 TortoiseGit
4 // Copyright (C) the libgit2 contributors. All rights reserved.
5 // - based on libgit2/src/transports/ssh.c
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "http_parser.h"
25 #include "../../ext/libgit2/src/transports/smart.h"
26 #include "system-call.h"
27 #include "ssh-wintunnel.h"
29 #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
31 static const char prefix_ssh
[] = "ssh://";
32 static const char cmd_uploadpack
[] = "git-upload-pack";
33 static const char cmd_receivepack
[] = "git-receive-pack";
36 git_smart_subtransport_stream parent
;
37 COMMAND_HANDLE commandHandle
;
43 git_smart_subtransport parent
;
44 transport_smart
*owner
;
45 ssh_stream
*current_stream
;
47 char *cmd_receivepack
;
53 * Create a git protocol request.
55 * For example: git-upload-pack '/libgit2/libgit2'
57 static int gen_proto(git_buf
*request
, const char *cmd
, const char *url
)
61 if (!git__prefixcmp(url
, prefix_ssh
)) {
62 url
= url
+ strlen(prefix_ssh
);
63 repo
= strchr(url
, '/');
65 repo
= strchr(url
, ':');
70 giterr_set(GITERR_NET
, "Malformed git protocol URL");
74 git_buf_printf(request
, "\"%s '%s'\"", cmd
, repo
);
76 if (git_buf_oom(request
)) {
84 static int ssh_stream_read(
85 git_smart_subtransport_stream
*stream
,
90 ssh_stream
*s
= (ssh_stream
*)stream
;
92 if (command_read_stdout(&s
->commandHandle
, buffer
, buf_size
, bytes_read
))
98 static int ssh_stream_write(
99 git_smart_subtransport_stream
*stream
,
103 ssh_stream
*s
= (ssh_stream
*)stream
;
105 if (command_write(&s
->commandHandle
, buffer
, len
))
111 static void ssh_stream_free(git_smart_subtransport_stream
*stream
)
113 ssh_stream
*s
= (ssh_stream
*)stream
;
114 ssh_subtransport
*t
= OWNING_SUBTRANSPORT(s
);
116 DWORD exitcode
= command_close(&s
->commandHandle
);
117 if (s
->commandHandle
.errBuf
) {
118 if (exitcode
&& exitcode
!= MAXDWORD
) {
119 if (!git_buf_oom(s
->commandHandle
.errBuf
) && git_buf_len(s
->commandHandle
.errBuf
))
120 giterr_set(GITERR_SSH
, "Command exited non-zero (%ld) and returned:\n%s", exitcode
, s
->commandHandle
.errBuf
->ptr
);
122 giterr_set(GITERR_SSH
, "Command exited non-zero: %ld", exitcode
);
124 git_buf_free(s
->commandHandle
.errBuf
);
125 git__free(s
->commandHandle
.errBuf
);
128 t
->current_stream
= NULL
;
134 static int ssh_stream_alloc(
138 git_smart_subtransport_stream
**stream
)
145 s
= git__calloc(sizeof(ssh_stream
), 1);
151 errBuf
= git__calloc(sizeof(git_buf
), 1);
158 s
->parent
.subtransport
= &t
->parent
;
159 s
->parent
.read
= ssh_stream_read
;
160 s
->parent
.write
= ssh_stream_write
;
161 s
->parent
.free
= ssh_stream_free
;
165 s
->url
= git__strdup(url
);
172 command_init(&s
->commandHandle
);
173 s
->commandHandle
.errBuf
= errBuf
;
175 *stream
= &s
->parent
;
179 static int git_ssh_extract_url_parts(
184 const char *colon
, *at
;
187 colon
= strchr(url
, ':');
189 at
= strchr(url
, '@');
192 *username
= git__substrdup(url
, at
- url
);
202 if (colon
== NULL
|| (colon
< start
)) {
203 giterr_set(GITERR_NET
, "Malformed URL");
207 *host
= git__substrdup(start
, colon
- start
);
216 static int wcstristr(const wchar_t *heystack
, const wchar_t *needle
)
219 size_t lenNeedle
= wcslen(needle
);
220 size_t lenHeystack
= wcslen(heystack
);
221 if (lenNeedle
> lenHeystack
)
224 end
= heystack
+ lenHeystack
- lenNeedle
;
225 while (heystack
<= end
) {
226 if (!wcsnicmp(heystack
, needle
, lenNeedle
))
234 /* based on netops.c code of libgit2, needed by extract_url_parts, see comment there */
235 #define hex2c(c) ((c | 32) % 39 - 9)
236 static char* unescape(char *str
)
239 int len
= (int)strlen(str
);
241 for (x
= y
= 0; str
[y
]; ++x
, ++y
) {
242 if ((str
[x
] = str
[y
]) == '%') {
243 if (y
< len
- 2 && isxdigit(str
[y
+ 1]) && isxdigit(str
[y
+ 2])) {
244 str
[x
] = (hex2c(str
[y
+ 1]) << 4) + hex2c(str
[y
+ 2]);
253 /* based on gitno_extract_url_parts, keep c&p copy here until https://github.com/libgit2/libgit2/pull/2492 gets merged or forever ;)*/
254 static int extract_url_parts(
261 const char *default_port
)
263 struct http_parser_url u
= { 0 };
264 const char *_host
, *_port
, *_path
, *_userinfo
;
266 if (http_parser_parse_url(url
, strlen(url
), false, &u
)) {
267 giterr_set(GITERR_NET
, "Malformed URL '%s'", url
);
268 return GIT_EINVALIDSPEC
;
271 _host
= url
+ u
.field_data
[UF_HOST
].off
;
272 _port
= url
+ u
.field_data
[UF_PORT
].off
;
273 _path
= url
+ u
.field_data
[UF_PATH
].off
;
274 _userinfo
= url
+ u
.field_data
[UF_USERINFO
].off
;
276 if (u
.field_set
& (1 << UF_HOST
)) {
277 *host
= git__substrdup(_host
, u
.field_data
[UF_HOST
].len
);
278 GITERR_CHECK_ALLOC(*host
);
281 if (u
.field_set
& (1 << UF_PORT
)) {
282 *port
= git__substrdup(_port
, u
.field_data
[UF_PORT
].len
);
283 } else if (default_port
) {
284 *port
= git__strdup(default_port
);
285 GITERR_CHECK_ALLOC(*port
);
290 if (u
.field_set
& (1 << UF_PATH
)) {
291 *path
= git__substrdup(_path
, u
.field_data
[UF_PATH
].len
);
292 GITERR_CHECK_ALLOC(*path
);
294 giterr_set(GITERR_NET
, "invalid url, missing path");
295 return GIT_EINVALIDSPEC
;
298 if (u
.field_set
& (1 << UF_USERINFO
)) {
299 const char *colon
= memchr(_userinfo
, ':', u
.field_data
[UF_USERINFO
].len
);
301 *username
= unescape(git__substrdup(_userinfo
, colon
- _userinfo
));
302 *password
= unescape(git__substrdup(colon
+ 1, u
.field_data
[UF_USERINFO
].len
- (colon
+ 1 - _userinfo
)));
303 GITERR_CHECK_ALLOC(*password
);
305 *username
= git__substrdup(_userinfo
, u
.field_data
[UF_USERINFO
].len
);
307 GITERR_CHECK_ALLOC(*username
);
314 static int _git_ssh_setup_tunnel(
318 git_smart_subtransport_stream
**stream
)
320 char *host
= NULL
, *port
= NULL
, *path
= NULL
, *user
= NULL
, *pass
= NULL
;
322 wchar_t *ssh
= t
->sshtoolpath
;
323 wchar_t *wideParams
= NULL
;
325 git_buf params
= GIT_BUF_INIT
;
330 if (ssh_stream_alloc(t
, url
, gitCmd
, stream
) < 0) {
335 s
= (ssh_stream
*)*stream
;
337 if (!git__prefixcmp(url
, prefix_ssh
)) {
338 if (extract_url_parts(&host
, &port
, &path
, &user
, &pass
, url
, NULL
) < 0)
341 if (git_ssh_extract_url_parts(&host
, &user
, url
) < 0)
347 giterr_set(GITERR_SSH
, "No GIT_SSH tool configured");
351 isPutty
= wcstristr(ssh
, L
"plink");
354 git_buf_printf(¶ms
, " -P %s", port
);
356 git_buf_printf(¶ms
, " -p %s", port
);
358 if (isPutty
&& !wcstristr(ssh
, L
"tortoiseplink")) {
359 git_buf_puts(¶ms
, " -batch");
362 git_buf_printf(¶ms
, " %s@%s ", user
, host
);
364 git_buf_printf(¶ms
, " %s ", host
);
365 if (gen_proto(¶ms
, s
->cmd
, s
->url
))
367 if (git_buf_oom(¶ms
)) {
372 if (git__utf8_to_16_alloc(&wideParams
, params
.ptr
) < 0) {
376 git_buf_free(¶ms
);
378 length
= wcslen(ssh
) + wcslen(wideParams
) + 3;
379 cmd
= git__calloc(length
, sizeof(wchar_t));
385 wcscat_s(cmd
, length
, L
"\"");
386 wcscat_s(cmd
, length
, ssh
);
387 wcscat_s(cmd
, length
, L
"\"");
388 wcscat_s(cmd
, length
, wideParams
);
390 if (command_start(cmd
, &s
->commandHandle
, t
->pEnv
, isPutty
? CREATE_NEW_CONSOLE
: DETACHED_PROCESS
))
393 git__free(wideParams
);
395 t
->current_stream
= s
;
405 t
->current_stream
= NULL
;
408 ssh_stream_free(*stream
);
410 git_buf_free(¶ms
);
413 git__free(wideParams
);
426 static int ssh_uploadpack_ls(
429 git_smart_subtransport_stream
**stream
)
431 const char *cmd
= t
->cmd_uploadpack
? t
->cmd_uploadpack
: cmd_uploadpack
;
433 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
439 static int ssh_uploadpack(
442 git_smart_subtransport_stream
**stream
)
446 if (t
->current_stream
) {
447 *stream
= &t
->current_stream
->parent
;
451 giterr_set(GITERR_NET
, "Must call UPLOADPACK_LS before UPLOADPACK");
455 static int ssh_receivepack_ls(
458 git_smart_subtransport_stream
**stream
)
460 const char *cmd
= t
->cmd_receivepack
? t
->cmd_receivepack
: cmd_receivepack
;
462 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
468 static int ssh_receivepack(
471 git_smart_subtransport_stream
**stream
)
475 if (t
->current_stream
) {
476 *stream
= &t
->current_stream
->parent
;
480 giterr_set(GITERR_NET
, "Must call RECEIVEPACK_LS before RECEIVEPACK");
484 static int _ssh_action(
485 git_smart_subtransport_stream
**stream
,
486 git_smart_subtransport
*subtransport
,
488 git_smart_service_t action
)
490 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
493 case GIT_SERVICE_UPLOADPACK_LS
:
494 return ssh_uploadpack_ls(t
, url
, stream
);
496 case GIT_SERVICE_UPLOADPACK
:
497 return ssh_uploadpack(t
, url
, stream
);
499 case GIT_SERVICE_RECEIVEPACK_LS
:
500 return ssh_receivepack_ls(t
, url
, stream
);
502 case GIT_SERVICE_RECEIVEPACK
:
503 return ssh_receivepack(t
, url
, stream
);
510 static int _ssh_close(git_smart_subtransport
*subtransport
)
512 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
514 assert(!t
->current_stream
);
519 static void _ssh_free(git_smart_subtransport
*subtransport
)
521 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
523 assert(!t
->current_stream
);
525 git__free(t
->cmd_uploadpack
);
526 git__free(t
->cmd_receivepack
);
527 git__free(t
->sshtoolpath
);
531 int git_smart_subtransport_ssh_wintunnel(
532 git_smart_subtransport
**out
, git_transport
*owner
, LPCWSTR sshtoolpath
, LPWSTR pEnv
)
538 t
= git__calloc(sizeof(ssh_subtransport
), 1);
544 t
->sshtoolpath
= wcsdup(sshtoolpath
);
547 t
->owner
= (transport_smart
*)owner
;
548 t
->parent
.action
= _ssh_action
;
549 t
->parent
.close
= _ssh_close
;
550 t
->parent
.free
= _ssh_free
;
552 *out
= (git_smart_subtransport
*) t
;