2 * Copyright (c) 2018, 2019 Ori Bernstein <ori@openbsd.org>
3 * Copyright (c) 2021 Stefan Sperling <stsp@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/queue.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
30 #include "got_error.h"
33 #include "got_compat.h"
35 #include "got_lib_dial.h"
39 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
43 #define ssizeof(_x) ((ssize_t)(sizeof(_x)))
47 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
50 #ifndef GOT_DIAL_PATH_SSH
51 #define GOT_DIAL_PATH_SSH "/usr/bin/ssh"
55 #define GOT_DEFAULT_GIT_PORT 9418
56 #define GOT_DEFAULT_GIT_PORT_STR "9418"
58 const struct got_error
*
59 got_dial_apply_unveil(const char *proto
)
61 if (strcmp(proto
, "git+ssh") == 0 || strcmp(proto
, "ssh") == 0) {
62 if (unveil(GOT_DIAL_PATH_SSH
, "x") != 0) {
63 return got_error_from_errno2("unveil",
72 hassuffix(const char *base
, const char *suf
)
78 if (ns
<= nb
&& strcmp(base
+ (nb
- ns
), suf
) == 0)
83 const struct got_error
*
84 got_dial_parse_uri(char **proto
, char **host
, char **port
,
85 char **server_path
, char **repo_name
, const char *uri
)
87 const struct got_error
*err
= NULL
;
90 *proto
= *host
= *port
= *server_path
= *repo_name
= NULL
;
92 p
= strstr(uri
, "://");
94 /* Try parsing Git's "scp" style URL syntax. */
95 *proto
= strdup("ssh");
97 err
= got_error_from_errno("strdup");
103 err
= got_error(GOT_ERR_PARSE_URI
);
106 /* No slashes allowed before first colon. */
109 err
= got_error(GOT_ERR_PARSE_URI
);
112 *host
= strndup(s
, q
- s
);
114 err
= got_error_from_errno("strndup");
117 if ((*host
)[0] == '\0') {
118 err
= got_error(GOT_ERR_PARSE_URI
);
123 *proto
= strndup(uri
, p
- uri
);
124 if (*proto
== NULL
) {
125 err
= got_error_from_errno("strndup");
131 if (p
== NULL
|| strlen(p
) == 1) {
132 err
= got_error(GOT_ERR_PARSE_URI
);
136 q
= memchr(s
, ':', p
- s
);
138 *host
= strndup(s
, q
- s
);
140 err
= got_error_from_errno("strndup");
143 if ((*host
)[0] == '\0') {
144 err
= got_error(GOT_ERR_PARSE_URI
);
147 *port
= strndup(q
+ 1, p
- (q
+ 1));
149 err
= got_error_from_errno("strndup");
152 if ((*port
)[0] == '\0') {
153 err
= got_error(GOT_ERR_PARSE_URI
);
157 *host
= strndup(s
, p
- s
);
159 err
= got_error_from_errno("strndup");
162 if ((*host
)[0] == '\0') {
163 err
= got_error(GOT_ERR_PARSE_URI
);
169 while (p
[0] == '/' && p
[1] == '/')
171 *server_path
= strdup(p
);
172 if (*server_path
== NULL
) {
173 err
= got_error_from_errno("strdup");
176 got_path_strip_trailing_slashes(*server_path
);
177 if ((*server_path
)[0] == '\0') {
178 err
= got_error(GOT_ERR_PARSE_URI
);
182 err
= got_path_basename(repo_name
, *server_path
);
185 if (hassuffix(*repo_name
, ".git"))
186 (*repo_name
)[strlen(*repo_name
) - 4] = '\0';
187 if ((*repo_name
)[0] == '\0')
188 err
= got_error(GOT_ERR_PARSE_URI
);
205 const struct got_error
*
206 got_dial_ssh(pid_t
*newpid
, int *newfd
, const char *host
,
207 const char *port
, const char *path
, const char *direction
, int verbosity
)
209 const struct got_error
*error
= NULL
;
212 const char *argv
[11];
218 argv
[i
++] = GOT_DIAL_PATH_SSH
;
221 argv
[i
++] = (char *)port
;
223 if (verbosity
== -1) {
226 /* ssh(1) allows up to 3 "-v" options. */
227 for (j
= 0; j
< MIN(3, verbosity
); j
++)
231 argv
[i
++] = (char *)host
;
232 argv
[i
++] = (char *)cmd
;
233 argv
[i
++] = (char *)path
;
235 assert(i
<= nitems(argv
));
237 if (socketpair(AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
, pfd
) == -1)
238 return got_error_from_errno("socketpair");
242 error
= got_error_from_errno("fork");
246 } else if (pid
== 0) {
248 if (close(pfd
[1]) == -1)
250 if (dup2(pfd
[0], 0) == -1)
252 if (dup2(pfd
[0], 1) == -1)
254 n
= snprintf(cmd
, sizeof(cmd
), "git-%s-pack", direction
);
255 if (n
< 0 || n
>= ssizeof(cmd
))
257 if (execv(GOT_DIAL_PATH_SSH
, (char *const *)argv
) == -1)
259 abort(); /* not reached */
261 if (close(pfd
[0]) == -1)
262 return got_error_from_errno("close");
269 const struct got_error
*
270 got_dial_git(int *newfd
, const char *host
, const char *port
,
271 const char *path
, const char *direction
)
273 const struct got_error
*err
= NULL
;
274 struct addrinfo hints
, *servinfo
, *p
;
276 int fd
= -1, len
, r
, eaicode
;
281 port
= GOT_DEFAULT_GIT_PORT_STR
;
283 memset(&hints
, 0, sizeof hints
);
284 hints
.ai_family
= AF_UNSPEC
;
285 hints
.ai_socktype
= SOCK_STREAM
;
286 eaicode
= getaddrinfo(host
, port
, &hints
, &servinfo
);
289 snprintf(msg
, sizeof(msg
), "%s: %s", host
,
290 gai_strerror(eaicode
));
291 return got_error_msg(GOT_ERR_ADDRINFO
, msg
);
294 for (p
= servinfo
; p
!= NULL
; p
= p
->ai_next
) {
295 if ((fd
= socket(p
->ai_family
, p
->ai_socktype
,
296 p
->ai_protocol
)) == -1)
298 if (connect(fd
, p
->ai_addr
, p
->ai_addrlen
) == 0) {
302 err
= got_error_from_errno("connect");
305 freeaddrinfo(servinfo
);
309 if (asprintf(&cmd
, "git-%s-pack %s", direction
, path
) == -1) {
310 err
= got_error_from_errno("asprintf");
313 len
= 4 + strlen(cmd
) + 1 + strlen("host=") + strlen(host
) + 1;
314 r
= dprintf(fd
, "%04x%s%chost=%s%c", len
, cmd
, '\0', host
, '\0');
316 err
= got_error_from_errno("dprintf");