1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014, 2016 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 *ssh_prefixes
[] = { "ssh://", "ssh+git://", "git+ssh://" };
33 static const char cmd_uploadpack
[] = "git-upload-pack";
34 static const char cmd_receivepack
[] = "git-receive-pack";
37 git_smart_subtransport_stream parent
;
38 COMMAND_HANDLE commandHandle
;
44 git_smart_subtransport parent
;
45 transport_smart
*owner
;
46 ssh_stream
*current_stream
;
48 char *cmd_receivepack
;
54 * Create a git protocol request.
56 * For example: git-upload-pack '/libgit2/libgit2'
58 static int gen_proto(git_buf
*request
, const char *cmd
, const char *url
)
64 for (i
= 0; i
< ARRAY_SIZE(ssh_prefixes
); ++i
) {
65 const char *p
= ssh_prefixes
[i
];
67 if (!git__prefixcmp(url
, p
)) {
68 url
= url
+ strlen(p
);
69 repo
= strchr(url
, '/');
70 if (repo
&& repo
[1] == '~')
76 repo
= strchr(url
, ':');
81 giterr_set(GITERR_NET
, "Malformed git protocol URL");
85 git_buf_printf(request
, "\"%s '%s'\"", cmd
, repo
);
87 if (git_buf_oom(request
)) {
95 static int ssh_stream_read(
96 git_smart_subtransport_stream
*stream
,
101 ssh_stream
*s
= (ssh_stream
*)stream
;
103 if (command_read_stdout(&s
->commandHandle
, buffer
, buf_size
, bytes_read
))
109 static int ssh_stream_write(
110 git_smart_subtransport_stream
*stream
,
114 ssh_stream
*s
= (ssh_stream
*)stream
;
116 if (command_write(&s
->commandHandle
, buffer
, len
))
122 static void ssh_stream_free(git_smart_subtransport_stream
*stream
)
124 ssh_stream
*s
= (ssh_stream
*)stream
;
125 ssh_subtransport
*t
= OWNING_SUBTRANSPORT(s
);
127 DWORD exitcode
= command_close(&s
->commandHandle
);
128 if (s
->commandHandle
.errBuf
) {
129 if (exitcode
&& exitcode
!= MAXDWORD
) {
130 if (!git_buf_oom(s
->commandHandle
.errBuf
) && git_buf_len(s
->commandHandle
.errBuf
))
131 giterr_set(GITERR_SSH
, "Command exited non-zero (%ld) and returned:\n%s", exitcode
, s
->commandHandle
.errBuf
->ptr
);
133 giterr_set(GITERR_SSH
, "Command exited non-zero: %ld", exitcode
);
135 git_buf_free(s
->commandHandle
.errBuf
);
136 git__free(s
->commandHandle
.errBuf
);
139 t
->current_stream
= NULL
;
145 static int ssh_stream_alloc(
149 git_smart_subtransport_stream
**stream
)
156 s
= git__calloc(sizeof(ssh_stream
), 1);
162 errBuf
= git__calloc(sizeof(git_buf
), 1);
169 s
->parent
.subtransport
= &t
->parent
;
170 s
->parent
.read
= ssh_stream_read
;
171 s
->parent
.write
= ssh_stream_write
;
172 s
->parent
.free
= ssh_stream_free
;
176 s
->url
= git__strdup(url
);
184 command_init(&s
->commandHandle
);
185 s
->commandHandle
.errBuf
= errBuf
;
187 *stream
= &s
->parent
;
191 static int git_ssh_extract_url_parts(
196 const char *colon
, *at
;
199 colon
= strchr(url
, ':');
201 at
= strchr(url
, '@');
204 *username
= git__substrdup(url
, at
- url
);
214 if (colon
== NULL
|| (colon
< start
)) {
215 giterr_set(GITERR_NET
, "Malformed URL");
219 *host
= git__substrdup(start
, colon
- start
);
228 static int wcstristr(const wchar_t *heystack
, const wchar_t *needle
)
231 size_t lenNeedle
= wcslen(needle
);
232 size_t lenHeystack
= wcslen(heystack
);
233 if (lenNeedle
> lenHeystack
)
236 end
= heystack
+ lenHeystack
- lenNeedle
;
237 while (heystack
<= end
) {
238 if (!wcsnicmp(heystack
, needle
, lenNeedle
))
246 /* based on netops.c code of libgit2, needed by extract_url_parts, see comment there */
247 #define hex2c(c) ((c | 32) % 39 - 9)
248 static char* unescape(char *str
)
251 int len
= (int)strlen(str
);
253 for (x
= y
= 0; str
[y
]; ++x
, ++y
) {
254 if ((str
[x
] = str
[y
]) == '%') {
255 if (y
< len
- 2 && isxdigit(str
[y
+ 1]) && isxdigit(str
[y
+ 2])) {
256 str
[x
] = (hex2c(str
[y
+ 1]) << 4) + hex2c(str
[y
+ 2]);
265 /* based on gitno_extract_url_parts, keep c&p copy here until https://github.com/libgit2/libgit2/pull/2492 gets merged or forever ;)*/
266 static int extract_url_parts(
273 const char *default_port
)
275 struct http_parser_url u
= { 0 };
276 const char *_host
, *_port
, *_path
, *_userinfo
;
278 if (http_parser_parse_url(url
, strlen(url
), false, &u
)) {
279 giterr_set(GITERR_NET
, "Malformed URL '%s'", url
);
280 return GIT_EINVALIDSPEC
;
283 _host
= url
+ u
.field_data
[UF_HOST
].off
;
284 _port
= url
+ u
.field_data
[UF_PORT
].off
;
285 _path
= url
+ u
.field_data
[UF_PATH
].off
;
286 _userinfo
= url
+ u
.field_data
[UF_USERINFO
].off
;
288 if (u
.field_set
& (1 << UF_HOST
)) {
289 *host
= git__substrdup(_host
, u
.field_data
[UF_HOST
].len
);
290 GITERR_CHECK_ALLOC(*host
);
293 if (u
.field_set
& (1 << UF_PORT
)) {
294 *port
= git__substrdup(_port
, u
.field_data
[UF_PORT
].len
);
295 } else if (default_port
) {
296 *port
= git__strdup(default_port
);
297 GITERR_CHECK_ALLOC(*port
);
302 if (u
.field_set
& (1 << UF_PATH
)) {
303 *path
= git__substrdup(_path
, u
.field_data
[UF_PATH
].len
);
304 GITERR_CHECK_ALLOC(*path
);
306 giterr_set(GITERR_NET
, "invalid url, missing path");
307 return GIT_EINVALIDSPEC
;
310 if (u
.field_set
& (1 << UF_USERINFO
)) {
311 const char *colon
= memchr(_userinfo
, ':', u
.field_data
[UF_USERINFO
].len
);
313 *username
= unescape(git__substrdup(_userinfo
, colon
- _userinfo
));
314 *password
= unescape(git__substrdup(colon
+ 1, u
.field_data
[UF_USERINFO
].len
- (colon
+ 1 - _userinfo
)));
315 GITERR_CHECK_ALLOC(*password
);
317 *username
= git__substrdup(_userinfo
, u
.field_data
[UF_USERINFO
].len
);
319 GITERR_CHECK_ALLOC(*username
);
326 static int _git_ssh_setup_tunnel(
330 git_smart_subtransport_stream
**stream
)
332 char *host
= NULL
, *port
= NULL
, *path
= NULL
, *user
= NULL
, *pass
= NULL
;
335 wchar_t *ssh
= t
->sshtoolpath
;
336 wchar_t *wideParams
= NULL
;
338 git_buf params
= GIT_BUF_INIT
;
343 if (ssh_stream_alloc(t
, url
, gitCmd
, stream
) < 0) {
348 s
= (ssh_stream
*)*stream
;
350 for (i
= 0; i
< ARRAY_SIZE(ssh_prefixes
); ++i
) {
351 const char *p
= ssh_prefixes
[i
];
353 if (!git__prefixcmp(url
, p
)) {
354 if (extract_url_parts(&host
, &port
, &path
, &user
, &pass
, url
, NULL
) < 0)
360 if (git_ssh_extract_url_parts(&host
, &user
, url
) < 0)
366 giterr_set(GITERR_SSH
, "No GIT_SSH tool configured");
370 isPutty
= wcstristr(ssh
, L
"plink");
373 git_buf_printf(¶ms
, " -P %s", port
);
375 git_buf_printf(¶ms
, " -p %s", port
);
377 if (isPutty
&& !wcstristr(ssh
, L
"tortoiseplink")) {
378 git_buf_puts(¶ms
, " -batch");
381 git_buf_printf(¶ms
, " %s@%s ", user
, host
);
383 git_buf_printf(¶ms
, " %s ", host
);
384 if (gen_proto(¶ms
, s
->cmd
, s
->url
))
386 if (git_buf_oom(¶ms
)) {
391 if (git__utf8_to_16_alloc(&wideParams
, params
.ptr
) < 0) {
395 git_buf_free(¶ms
);
397 length
= wcslen(ssh
) + wcslen(wideParams
) + 3;
398 cmd
= git__calloc(length
, sizeof(wchar_t));
404 wcscat_s(cmd
, length
, L
"\"");
405 wcscat_s(cmd
, length
, ssh
);
406 wcscat_s(cmd
, length
, L
"\"");
407 wcscat_s(cmd
, length
, wideParams
);
409 if (command_start(cmd
, &s
->commandHandle
, t
->pEnv
, isPutty
? CREATE_NEW_CONSOLE
: DETACHED_PROCESS
))
412 git__free(wideParams
);
414 t
->current_stream
= s
;
424 t
->current_stream
= NULL
;
427 ssh_stream_free(*stream
);
429 git_buf_free(¶ms
);
432 git__free(wideParams
);
445 static int ssh_uploadpack_ls(
448 git_smart_subtransport_stream
**stream
)
450 const char *cmd
= t
->cmd_uploadpack
? t
->cmd_uploadpack
: cmd_uploadpack
;
452 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
458 static int ssh_uploadpack(
461 git_smart_subtransport_stream
**stream
)
465 if (t
->current_stream
) {
466 *stream
= &t
->current_stream
->parent
;
470 giterr_set(GITERR_NET
, "Must call UPLOADPACK_LS before UPLOADPACK");
474 static int ssh_receivepack_ls(
477 git_smart_subtransport_stream
**stream
)
479 const char *cmd
= t
->cmd_receivepack
? t
->cmd_receivepack
: cmd_receivepack
;
481 if (_git_ssh_setup_tunnel(t
, url
, cmd
, stream
) < 0)
487 static int ssh_receivepack(
490 git_smart_subtransport_stream
**stream
)
494 if (t
->current_stream
) {
495 *stream
= &t
->current_stream
->parent
;
499 giterr_set(GITERR_NET
, "Must call RECEIVEPACK_LS before RECEIVEPACK");
503 static int _ssh_action(
504 git_smart_subtransport_stream
**stream
,
505 git_smart_subtransport
*subtransport
,
507 git_smart_service_t action
)
509 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
512 case GIT_SERVICE_UPLOADPACK_LS
:
513 return ssh_uploadpack_ls(t
, url
, stream
);
515 case GIT_SERVICE_UPLOADPACK
:
516 return ssh_uploadpack(t
, url
, stream
);
518 case GIT_SERVICE_RECEIVEPACK_LS
:
519 return ssh_receivepack_ls(t
, url
, stream
);
521 case GIT_SERVICE_RECEIVEPACK
:
522 return ssh_receivepack(t
, url
, stream
);
529 static int _ssh_close(git_smart_subtransport
*subtransport
)
531 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
533 assert(!t
->current_stream
);
538 static void _ssh_free(git_smart_subtransport
*subtransport
)
540 ssh_subtransport
*t
= (ssh_subtransport
*) subtransport
;
542 assert(!t
->current_stream
);
544 git__free(t
->cmd_uploadpack
);
545 git__free(t
->cmd_receivepack
);
546 git__free(t
->sshtoolpath
);
550 int git_smart_subtransport_ssh_wintunnel(
551 git_smart_subtransport
**out
, git_transport
*owner
, LPCWSTR sshtoolpath
, LPWSTR
* pEnv
)
557 t
= git__calloc(sizeof(ssh_subtransport
), 1);
563 t
->sshtoolpath
= wcsdup(sshtoolpath
);
566 t
->owner
= (transport_smart
*)owner
;
567 t
->parent
.action
= _ssh_action
;
568 t
->parent
.close
= _ssh_close
;
569 t
->parent
.free
= _ssh_free
;
571 *out
= (git_smart_subtransport
*) t
;