2 * Copyright (C) Ilari Liusvaara 2009-2010
4 * This code is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
9 #include "srp_askpass.h"
21 #include <gnutls/gnutls.h>
23 #include "git-compat-util.h"
25 volatile sig_atomic_t in_handler
= 0;
26 volatile sig_atomic_t sigusr1_flag
= 0;
27 struct user
*debug_for
= NULL
;
31 static void sigusr1_handler(int x
)
37 user_debug(debug_for
, stderr
);
42 char *protocol
; /* Protocol part */
43 char *user
; /* User part, NULL if no user */
44 char *host
; /* Hostname */
45 char *uservid
; /* Unique server ID */
46 char *port
; /* Port as string, NULL if no port */
47 char *path
; /* Path part. */
48 char *vhost_header
; /* vhost header to send */
49 char *transport_proto
; /* Transport protocol. */
50 char *address_space
; /* Address space to use. May be NULL */
53 void append_uniq_address(char *buffer
, struct parsed_addr
*_addr
,
57 ptr
= strchr(_addr
->host
, '~');
65 if (strchr(_addr
->host
, ':'))
69 strcat(buffer
, _addr
->port
);
73 struct parsed_addr
parse_address(const char *addr
)
75 struct parsed_addr _addr
;
76 const char *proto_end
;
77 const char *path_start
;
78 const char *uhp_start
;
79 const char *uhp_delim
;
80 const char *orig_addr
= addr
;
81 const char *proto_sep
;
83 int nondefault_port
= 0;
86 _addr
.transport_proto
= xstrdup("tcp");
87 _addr
.address_space
= NULL
;
89 addrlen
= strlen(addr
);
91 proto_end
= strchr(addr
, ':');
93 die("URL '%s': No ':' to end protocol.", orig_addr
);
95 _addr
.protocol
= xstrndup(addr
, proto_end
- addr
);
96 if (strncmp(proto_end
, "://", 3))
97 die("URL '%s': No '://' to end protocol.", orig_addr
);
99 uhp_start
= proto_end
+ 3;
101 /* Figure out the user if any. */
102 uhp_delim
= strpbrk(uhp_start
, "@[:/");
104 if (*uhp_delim
== '@') {
105 _addr
.user
= xstrndup(uhp_start
,
106 uhp_delim
- uhp_start
);
107 uhp_start
= uhp_delim
+ 1;
112 /* Figure out host. */
113 if (*uhp_start
== '[') {
114 uhp_delim
= strpbrk(uhp_start
, "]");
116 _addr
.host
= xstrndup(uhp_start
+ 1,
117 uhp_delim
- uhp_start
- 1);
118 if (uhp_delim
[1] != ':' && uhp_delim
[1] != '/')
119 die("URL '%s': Expected port or path after hostname",
121 uhp_start
= uhp_delim
+ 1;
123 die("URL '%s': Hostname has '[' without matching ']'",
126 uhp_delim
= strpbrk(uhp_start
, "[:/");
127 if (*uhp_delim
== '[')
128 die("URL '%s': Unexpected '['", orig_addr
);
129 _addr
.host
= xstrndup(uhp_start
, uhp_delim
- uhp_start
);
130 uhp_start
= uhp_delim
;
133 _addr
.uservid
= NULL
;
134 proto_sep
= strchr(_addr
.host
, '@');
135 if (proto_sep
&& proto_sep
!= _addr
.host
)
138 old_host
= _addr
.host
;
139 _addr
.uservid
= xstrndup(_addr
.host
, proto_sep
- _addr
.host
);
140 _addr
.host
= xstrdup(proto_sep
+ 1);
145 proto_sep
= strchr(_addr
.host
, '~');
148 free(_addr
.transport_proto
);
149 free(_addr
.address_space
);
153 old_host
= _addr
.host
;
154 ptr2
= strchr(old_host
, '/');
155 if (ptr2
>= proto_sep
)
159 _addr
.transport_proto
= xstrndup(_addr
.host
,
161 _addr
.address_space
= xstrndup(ptr2
+ 1,
162 proto_sep
- ptr2
- 1);
163 } else if (proto_sep
== old_host
+ 4 &&
164 !strncmp(old_host
, "unix", 4)) {
165 _addr
.transport_proto
= xstrdup("<N/A>");
166 _addr
.address_space
= xstrdup("unix");
169 if(proto_sep
> old_host
)
170 suffix
= proto_sep
[-1];
172 _addr
.transport_proto
= xstrndup(
173 old_host
, proto_sep
- old_host
- 1);
174 _addr
.address_space
= xstrdup("ipv4");
175 } else if(suffix
== '6') {
176 _addr
.transport_proto
= xstrndup(
177 old_host
, proto_sep
- old_host
- 1);
178 _addr
.address_space
= xstrdup("ipv6");
179 } else if(suffix
== '_') {
180 _addr
.transport_proto
= xstrndup(
181 old_host
, proto_sep
- old_host
- 1);
182 _addr
.address_space
= NULL
;
184 _addr
.transport_proto
= xstrndup(
185 old_host
, proto_sep
- old_host
);
186 _addr
.address_space
= NULL
;
189 _addr
.host
= xstrdup(proto_sep
+ 1);
193 path_start
= strchr(uhp_start
, '/');
195 die("URL '%s': No '/' to start path", orig_addr
);
197 _addr
.path
= xstrndup(path_start
, addrlen
- (path_start
- addr
));
199 if (*uhp_start
== ':')
200 _addr
.port
= xstrndup(uhp_start
+ 1,
201 path_start
- uhp_start
- 1);
206 die("URL '%s': Empty hostname not allowed", orig_addr
);
208 if (strcmp(_addr
.protocol
, "gits") && strcmp(_addr
.protocol
, "tls") &&
209 strcmp(_addr
.protocol
, "git")) {
210 die("Unknown protocol %s://", _addr
.protocol
);
213 if (!strcmp(_addr
.protocol
, "git") && _addr
.user
) {
214 die("git:// does not support users");
217 if (!strcmp(_addr
.protocol
, "git") && _addr
.uservid
) {
218 die("git:// does not support unique server identitifier");
223 } else if (!strcmp(_addr
.protocol
, "gits")) {
224 _addr
.port
= xstrndup("git", 3);
225 } else if (!strcmp(_addr
.protocol
, "git")) {
226 _addr
.port
= xstrndup("git", 3);
227 } else if (!strcmp(_addr
.protocol
, "tls")) {
228 _addr
.port
= xstrndup("gits", 4);
231 /* 8 is for host=[]:\0 */
232 vhost_len
= 9 + strlen(_addr
.host
) + strlen(_addr
.port
);
233 _addr
.vhost_header
= xmalloc(vhost_len
);
235 strcpy(_addr
.vhost_header
, "host=");
236 append_uniq_address(_addr
.vhost_header
, &_addr
, !nondefault_port
);
239 fprintf(stderr
, "Protocol: %s\n", _addr
.protocol
);
240 fprintf(stderr
, "User: %s\n", _addr
.user
?
241 _addr
.user
: "<not set>");
242 fprintf(stderr
, "Host: %s\n", _addr
.host
);
243 fprintf(stderr
, "Unique server ID: %s\n", _addr
.uservid
?
244 _addr
.uservid
: "<any>");
245 fprintf(stderr
, "Port: %s\n", _addr
.port
);
246 fprintf(stderr
, "Path: %s\n", _addr
.path
);
247 fprintf(stderr
, "Vhost header: %s\n",
249 fprintf(stderr
, "Transport protocol: %s\n",
250 _addr
.transport_proto
);
251 fprintf(stderr
, "Address space: %s\n",
252 _addr
.address_space
? _addr
.address_space
:
254 fprintf(stderr
, "\n");
260 #define MODE_ALLOW_EOF 0
261 #define MODE_HANDSHAKE 1
264 static void disconnect_outgoing(struct user
*user
)
266 if(user_get_red_out(user
))
267 user_set_red_io(user
, -1, -1, -1);
269 user_set_red_io(user
, -1, 1, -1);
270 user_send_red_in_eof(user
);
273 static void traffic_loop(struct user
*user
, int mode
)
277 struct pollfd polled
[MAXFDS
];
280 struct timeval deadline
;
285 user_add_to_sets(user
, &bound
, &rfds
, &wfds
, &deadline
);
287 failcode
= user_get_failure(user
);
292 for(r
= 0; r
< bound
|| r
<= socket_fd
; r
++) {
294 polled
[fdcount
].fd
= r
;
295 polled
[fdcount
].events
= 0;
296 polled
[fdcount
].revents
= 0;
297 if(FD_ISSET(r
, &rfds
)) {
298 polled
[fdcount
].events
|= (POLLIN
| POLLHUP
);
301 if(FD_ISSET(r
, &wfds
)) {
302 polled
[fdcount
].events
|= (POLLOUT
| POLLHUP
);
306 polled
[fdcount
].events
|= POLLHUP
;
313 r
= poll(polled
, fdcount
, -1);
314 if (r
< 0 && errno
!= EINTR
) {
315 die_errno("poll() failed");
322 for(r
= 0; r
< fdcount
; r
++) {
323 if(polled
[r
].revents
& POLLHUP
) {
324 /* Write stream hangup. Disconnect. */
325 if(polled
[r
].fd
== 0)
327 if(polled
[r
].fd
== 1)
330 disconnect_outgoing(user
);
332 if(polled
[r
].revents
& POLLIN
)
333 FD_SET(polled
[r
].fd
, &rfds
);
334 if(polled
[r
].revents
& POLLOUT
)
335 FD_SET(polled
[r
].fd
, &wfds
);
339 user_service(user
, &rfds
, &wfds
);
343 user_debug(debug_for
, stderr
);
345 failcode
= user_get_failure(user
);
348 if (user_red_out_eofd(user
) && !cbuffer_used(
349 user_get_red_out_force(user
))) {
356 if (failcode
> 0 && mode
== MODE_ALLOW_EOF
)
358 else if (failcode
> 0) {
359 error("Expected more data, got connection closed");
360 fprintf(stderr
, "Debug dump:\n");
361 user_debug(user
, stderr
);
362 die("Expected more data, got connection closed");
363 } else if (failcode
< 0) {
367 major
= user_explain_failure(failcode
);
368 minor
= user_get_error(user
);
371 die("Connection lost: %s (%s)", major
, minor
);
373 die("Connection lost: %s", major
);
377 static int select_keypair(gnutls_certificate_credentials_t creds
,
378 const char *username
, int must_succeed
)
381 ret
= select_keypair_int(creds
, username
);
382 if (ret
< 0 && must_succeed
)
383 die("No keypair identity %s found", username
);
387 static gnutls_session_t session
;
389 static void preconfigure_tls(const char *username
)
392 gnutls_certificate_credentials_t creds
;
395 const char *srp_password
;
396 gnutls_srp_client_credentials_t srp_cred
;
400 s
= gnutls_global_init();
402 die("Can't initialize GnuTLS: %s", gnutls_strerror(s
));
404 s
= gnutls_certificate_allocate_credentials(&creds
);
406 die("Can't allocate cert creds: %s", gnutls_strerror(s
));
408 s
= gnutls_init(&session
, GNUTLS_CLIENT
);
410 die("Can't allocate session: %s", gnutls_strerror(s
));
413 s
= gnutls_priority_set_direct (session
, "NORMAL:+SRP-DSS:+SRP-RSA",
416 s
= gnutls_priority_set_direct (session
, "NORMAL", NULL
);
419 die("Can't set priority: %s", gnutls_strerror(s
));
422 if (!prefixcmp(username
, "ssh-")) {
423 do_ssh_preauth(username
+ 4);
427 if (!prefixcmp(username
, "key-")) {
428 select_keypair(creds
, username
+ 4, 1);
431 keypair_ok
= (select_keypair(creds
, username
, 0)
436 s
= gnutls_credentials_set (session
, GNUTLS_CRD_CERTIFICATE
, creds
);
438 die("Can't set creds: %s", gnutls_strerror(s
));
443 if (username
&& !prefixcmp(username
, "srp-"))
444 username
= username
+ 4;
445 if (!username
|| !*username
)
448 s
= gnutls_srp_allocate_client_credentials(&srp_cred
);
450 die("Can't allocate SRP creds: %s", gnutls_strerror(s
));
453 srp_password
= get_srp_password(username
);
454 s
= gnutls_srp_set_client_credentials(srp_cred
, username
, srp_password
);
456 die("Can't set SRP creds: %s", gnutls_strerror(s
));
458 s
= gnutls_credentials_set(session
, GNUTLS_CRD_SRP
, srp_cred
);
460 die("Can't use SRP creds: %s", gnutls_strerror(s
));
462 /* GnuTLS doesn't seem to like to use SRP. Force it. */
463 kx
[0] = GNUTLS_KX_SRP_DSS
;
464 kx
[1] = GNUTLS_KX_SRP_RSA
;
466 s
= gnutls_kx_set_priority(session
, kx
);
468 die("Can't force SRP: %s", gnutls_strerror(s
));
472 kx
[0] = GNUTLS_KX_DHE_DSS
;
473 kx
[1] = GNUTLS_KX_DHE_RSA
;
475 s
= gnutls_kx_set_priority(session
, kx
);
477 die("Can't force Diffie-Hellman: %s", gnutls_strerror(s
));
482 static void configure_tls(struct user
*user
, const char *hostname
)
484 user_configure_tls(user
, session
);
486 /* Wait for TLS connection to establish. */
487 while (!user_get_tls(user
))
488 traffic_loop(user
, MODE_HANDSHAKE
);
491 check_hostkey(session
, hostname
);
494 #define MAX_REQUEST 8192
495 const char *hexes
= "0123456789abcdef";
497 static void do_request(const char *arg
, struct parsed_addr
*addr
,
498 int suppress_ok
, int no_repo
)
501 struct user
*dispatcher
;
502 struct cbuffer
*inbuf
;
503 struct cbuffer
*outbuf
;
506 char reqbuf
[MAX_REQUEST
+ 4];
511 signal(SIGPIPE
, SIG_IGN
);
514 preconfigure_tls(addr
->user
);
516 fd
= connect_host(addr
->host
, addr
->port
,
517 addr
->transport_proto
, addr
->address_space
);
519 /* Create dispatcher with no time limit. */
520 debug_for
= dispatcher
= user_create(fd
, 65535);
522 die("Can't create connection context");
524 user_clear_deadline(dispatcher
);
526 inbuf
= user_get_red_in(dispatcher
);
527 outbuf
= user_get_red_out(dispatcher
);
528 if (!strcmp(addr
->protocol
, "git")) {
529 ; /* Not protected. */
530 } else if (!strcmp(addr
->protocol
, "tls")) {
532 fprintf(stderr
, "Configuring TLS...\n");
533 configure_tls(dispatcher
, addr
->uservid
);
537 fprintf(stderr
, "Requesting TLS...\n");
538 cbuffer_write(inbuf
, (unsigned char*)"000cstarttls", 12);
542 traffic_loop(dispatcher
, MODE_HANDSHAKE
);
543 s
= cbuffer_peek(outbuf
, (unsigned char*)tmpbuf
, 8);
545 if (s
>= 0 && !strcmp(tmpbuf
, "proceed\n"))
547 if (s
>= 0 && !strcmp(tmpbuf
, "notsupp\n"))
548 die("Server does not support gits://");
549 if (user_red_out_eofd(dispatcher
))
551 if (user_get_failure(dispatcher
))
555 fprintf(stderr
, "Server ready for TLS. Configuring TLS...\n");
556 configure_tls(dispatcher
, addr
->uservid
);
562 fprintf(stderr
, "Waiting for TLS link to establish...\n");
564 while (!user_get_tls(dispatcher
))
565 traffic_loop(dispatcher
, MODE_HANDSHAKE
);
568 fprintf(stderr
, "Secure link established.\n");
571 if (addr
->user
&& !prefixcmp(addr
->user
, "ssh-")) {
573 fprintf(stderr
, "Sending SSH auth request...\n");
574 send_ssh_authentication(dispatcher
, addr
->user
+ 4);
575 while (cbuffer_used(inbuf
))
576 traffic_loop(dispatcher
, MODE_HANDSHAKE
);
578 fprintf(stderr
, "Sent SSH auth request.\n");
582 reqsize
= strlen(arg
) + strlen(addr
->path
) + 3 +
583 strlen(addr
->vhost_header
);
585 reqsize
= strlen(arg
);
587 if (reqsize
> MAX_REQUEST
)
588 die("Request too big to send");
590 memcpy(reqbuf
+ 4, arg
, strlen(arg
));
592 reqbuf
[strlen(arg
) + 4] = ' ';
593 memcpy(reqbuf
+ strlen(arg
) + 5, addr
->path
, strlen(addr
->path
) + 1);
594 memcpy(reqbuf
+ strlen(arg
) + 6 + strlen(addr
->path
),
595 addr
->vhost_header
, strlen(addr
->vhost_header
) + 1);
598 reqbuf
[0] = hexes
[((reqsize
+ 4) >> 12) & 0xF];
599 reqbuf
[1] = hexes
[((reqsize
+ 4) >> 8) & 0xF];
600 reqbuf
[2] = hexes
[((reqsize
+ 4) >> 4) & 0xF];
601 reqbuf
[3] = hexes
[(reqsize
+ 4) & 0xF];
604 fprintf(stderr
, "Sending request...\n");
605 cbuffer_write(inbuf
, (unsigned char*)reqbuf
, reqsize
+ 4);
607 fprintf(stderr
, "Request sent, waiting for reply...\n");
608 while (!cbuffer_used(outbuf
) && !suppress_ok
)
609 traffic_loop(dispatcher
, MODE_HANDSHAKE
);
611 fprintf(stderr
, "Server replied.\n");
612 /* Ok, remote end has replied. */
616 user_set_red_io(dispatcher
, 0, 1, -1);
618 while (!user_get_failure(dispatcher
))
619 traffic_loop(dispatcher
, MODE_ALLOW_EOF
);
623 major
= user_explain_failure(user_get_failure(dispatcher
));
624 minor
= user_get_error(dispatcher
);
627 die("Connection lost: %s (%s)", major
, minor
);
629 die("Connection lost: %s", major
);
632 die("Expected response to starttls, server closed connection.");
636 int main(int argc
, char **argv
)
638 struct parsed_addr paddr
;
641 if (getenv("GITS_VERBOSE"))
645 die("Need two arguments");
648 signal(SIGUSR1
, sigusr1_handler
);
650 paddr
= parse_address(argv
[2]);
652 if (!prefixcmp(argv
[1], "--service=")) {
653 do_request(argv
[1] + 10, &paddr
, 1, 0);
656 if (!prefixcmp(argv
[1], "--nourl-service=")) {
657 do_request(argv
[1] + 16, &paddr
, 1, 1);
664 cmd
= fgets(buffer
, 8190, stdin
);
665 if (cmd
[strlen(cmd
) - 1] == '\n')
666 cmd
[strlen(cmd
) - 1] = '\0';
668 if (!strcmp(cmd
, "capabilities")) {
669 printf("*connect\n\n");
673 } else if (!prefixcmp(cmd
, "connect ")) {
674 do_request(cmd
+ 8, &paddr
, 0, 0);
677 die("Unknown command %s", cmd
);