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
);
173 command_init(&s
->commandHandle
);
174 s
->commandHandle
.errBuf
= errBuf
;
176 *stream
= &s
->parent
;
180 static int git_ssh_extract_url_parts(
185 const char *colon
, *at
;
188 colon
= strchr(url
, ':');
190 at
= strchr(url
, '@');
193 *username
= git__substrdup(url
, at
- url
);
203 if (colon
== NULL
|| (colon
< start
)) {
204 giterr_set(GITERR_NET
, "Malformed URL");
208 *host
= git__substrdup(start
, colon
- start
);
217 static int wcstristr(const wchar_t *heystack
, const wchar_t *needle
)
220 size_t lenNeedle
= wcslen(needle
);
221 size_t lenHeystack
= wcslen(heystack
);
222 if (lenNeedle
> lenHeystack
)
225 end
= heystack
+ lenHeystack
- lenNeedle
;
226 while (heystack
<= end
) {
227 if (!wcsnicmp(heystack
, needle
, lenNeedle
))
235 /* based on netops.c code of libgit2, needed by extract_url_parts, see comment there */
236 #define hex2c(c) ((c | 32) % 39 - 9)
237 static char* unescape(char *str
)
240 int len
= (int)strlen(str
);
242 for (x
= y
= 0; str
[y
]; ++x
, ++y
) {
243 if ((str
[x
] = str
[y
]) == '%') {
244 if (y
< len
- 2 && isxdigit(str
[y
+ 1]) && isxdigit(str
[y
+ 2])) {
245 str
[x
] = (hex2c(str
[y
+ 1]) << 4) + hex2c(str
[y
+ 2]);
254 /* based on gitno_extract_url_parts, keep c&p copy here until https://github.com/libgit2/libgit2/pull/2492 gets merged or forever ;)*/
255 static int extract_url_parts(
262 const char *default_port
)
264 struct http_parser_url u
= { 0 };
265 const char *_host
, *_port
, *_path
, *_userinfo
;
267 if (http_parser_parse_url(url
, strlen(url
), false, &u
)) {
268 giterr_set(GITERR_NET
, "Malformed URL '%s'", url
);
269 return GIT_EINVALIDSPEC
;
272 _host
= url
+ u
.field_data
[UF_HOST
].off
;
273 _port
= url
+ u
.field_data
[UF_PORT
].off
;
274 _path
= url
+ u
.field_data
[UF_PATH
].off
;
275 _userinfo
= url
+ u
.field_data
[UF_USERINFO
].off
;
277 if (u
.field_set
& (1 << UF_HOST
)) {
278 *host
= git__substrdup(_host
, u
.field_data
[UF_HOST
].len
);
279 GITERR_CHECK_ALLOC(*host
);
282 if (u
.field_set
& (1 << UF_PORT
)) {
283 *port
= git__substrdup(_port
, u
.field_data
[UF_PORT
].len
);
284 } else if (default_port
) {
285 *port
= git__strdup(default_port
);
286 GITERR_CHECK_ALLOC(*port
);
291 if (u
.field_set
& (1 << UF_PATH
)) {
292 *path
= git__substrdup(_path
, u
.field_data
[UF_PATH
].len
);
293 GITERR_CHECK_ALLOC(*path
);
295 giterr_set(GITERR_NET
, "invalid url, missing path");
296 return GIT_EINVALIDSPEC
;
299 if (u
.field_set
& (1 << UF_USERINFO
)) {
300 const char *colon
= memchr(_userinfo
, ':', u
.field_data
[UF_USERINFO
].len
);
302 *username
= unescape(git__substrdup(_userinfo
, colon
- _userinfo
));
303 *password
= unescape(git__substrdup(colon
+ 1, u
.field_data
[UF_USERINFO
].len
- (colon
+ 1 - _userinfo
)));
304 GITERR_CHECK_ALLOC(*password
);
306 *username
= git__substrdup(_userinfo
, u
.field_data
[UF_USERINFO
].len
);
308 GITERR_CHECK_ALLOC(*username
);
315 static int _git_ssh_setup_tunnel(
319 git_smart_subtransport_stream
**stream
)
321 char *host
= NULL
, *port
= NULL
, *path
= NULL
, *user
= NULL
, *pass
= NULL
;
323 wchar_t *ssh
= t
->sshtoolpath
;
324 wchar_t *wideParams
= NULL
;
326 git_buf params
= GIT_BUF_INIT
;
331 if (ssh_stream_alloc(t
, url
, gitCmd
, stream
) < 0) {
336 s
= (ssh_stream
*)*stream
;
338 if (!git__prefixcmp(url
, prefix_ssh
)) {
339 if (extract_url_parts(&host
, &port
, &path
, &user
, &pass
, url
, NULL
) < 0)
342 if (git_ssh_extract_url_parts(&host
, &user
, url
) < 0)
348 giterr_set(GITERR_SSH
, "No GIT_SSH tool configured");
352 isPutty
= wcstristr(ssh
, L
"plink");
355 git_buf_printf(¶ms
, " -P %s", port
);
357 git_buf_printf(¶ms
, " -p %s", port
);
359 if (isPutty
&& !wcstristr(ssh
, L
"tortoiseplink")) {
360 git_buf_puts(¶ms
, " -batch");
363 git_buf_printf(¶ms
, " %s@%s ", user
, host
);
365 git_buf_printf(¶ms
, " %s ", host
);
366 if (gen_proto(¶ms
, s
->cmd
, s
->url
))
368 if (git_buf_oom(¶ms
)) {
373 if (git__utf8_to_16_alloc(&wideParams
, params
.ptr
) < 0) {
377 git_buf_free(¶ms
);
379 length
= wcslen(ssh
) + wcslen(wideParams
) + 3;
380 cmd
= git__calloc(length
, sizeof(wchar_t));
386 wcscat_s(cmd
, length
, L
"\"");
387 wcscat_s(cmd
, length
, ssh
);
388 wcscat_s(cmd
, length
, L
"\"");
389 wcscat_s(cmd
, length
, wideParams
);
391 if (command_start(cmd
, &s
->commandHandle
, t
->pEnv
, isPutty
? CREATE_NEW_CONSOLE
: DETACHED_PROCESS
))
394 git__free(wideParams
);
396 t
->current_stream
= s
;
406 t
->current_stream
= NULL
;
409 ssh_stream_free(*stream
);
411 git_buf_free(¶ms
);
414 git__free(wideParams
);
427 static int ssh_uploadpack_ls(
430 git_smart_subtransport_stream
**stream
)
432 const char *cmd
= t
->cmd_uploadpack
? t
->cmd_uploadpack
: cmd_uploadpack
;
434 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
440 static int ssh_uploadpack(
443 git_smart_subtransport_stream
**stream
)
447 if (t
->current_stream
) {
448 *stream
= &t
->current_stream
->parent
;
452 giterr_set(GITERR_NET
, "Must call UPLOADPACK_LS before UPLOADPACK");
456 static int ssh_receivepack_ls(
459 git_smart_subtransport_stream
**stream
)
461 const char *cmd
= t
->cmd_receivepack
? t
->cmd_receivepack
: cmd_receivepack
;
463 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
469 static int ssh_receivepack(
472 git_smart_subtransport_stream
**stream
)
476 if (t
->current_stream
) {
477 *stream
= &t
->current_stream
->parent
;
481 giterr_set(GITERR_NET
, "Must call RECEIVEPACK_LS before RECEIVEPACK");
485 static int _ssh_action(
486 git_smart_subtransport_stream
**stream
,
487 git_smart_subtransport
*subtransport
,
489 git_smart_service_t action
)
491 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
494 case GIT_SERVICE_UPLOADPACK_LS
:
495 return ssh_uploadpack_ls(t
, url
, stream
);
497 case GIT_SERVICE_UPLOADPACK
:
498 return ssh_uploadpack(t
, url
, stream
);
500 case GIT_SERVICE_RECEIVEPACK_LS
:
501 return ssh_receivepack_ls(t
, url
, stream
);
503 case GIT_SERVICE_RECEIVEPACK
:
504 return ssh_receivepack(t
, url
, stream
);
511 static int _ssh_close(git_smart_subtransport
*subtransport
)
513 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
515 assert(!t
->current_stream
);
520 static void _ssh_free(git_smart_subtransport
*subtransport
)
522 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
524 assert(!t
->current_stream
);
526 git__free(t
->cmd_uploadpack
);
527 git__free(t
->cmd_receivepack
);
528 git__free(t
->sshtoolpath
);
532 int git_smart_subtransport_ssh_wintunnel(
533 git_smart_subtransport
**out
, git_transport
*owner
, LPCWSTR sshtoolpath
, LPWSTR pEnv
)
539 t
= git__calloc(sizeof(ssh_subtransport
), 1);
545 t
->sshtoolpath
= wcsdup(sshtoolpath
);
548 t
->owner
= (transport_smart
*)owner
;
549 t
->parent
.action
= _ssh_action
;
550 t
->parent
.close
= _ssh_close
;
551 t
->parent
.free
= _ssh_free
;
553 *out
= (git_smart_subtransport
*) t
;