Rationale ======= Many open source projects provide access to their source code via remote cvs (usually pserver). Unfortunately, many people are prevented from using these facilities because they are behind firewalls which prevent uncontrolled tcp access outside of their LAN. However, this is also often the case for things like HTTPS and so Netscape have defined a way of circumventing these restrictions by tunnelling connections through HTTP. See http://home.netscape.com/newsref/std/tunneling_ssl.html. This facility can easily be added to cvs and the attached patch achieves this. To use the proxy www on port 8080 to connect to the external machine there.somedomain use: cvs -d :pserver;proxy=www;proxyport=8080:me@there.somedomain:/somerepository The patch includes Jim Kingdon's port patch as many web servers restrict the use of CONNECT to well known ports. A way of circumventing this is to make a cvs server listen on one of these ports - for instance 443 the HTTPS port. To access a server set up in this way do: cvs -d :pserver;proxy=www;proxyport=8080;port=443:me@there.somedomain:/somerepository ChangeLog and patch follow. The patch was inspired by ssh-tunnel.pl written by Urban Kaveus . 1998-12-26 Andy Piper and Jim Kingdon * cvs.h: declare proxy variables CVSroot_proxy, CVSroot_proxy_port, CVSroot_port. * root.c: define proxy variables. (parse_cvsroot): pick up semicolon expressions port=, proxy=, proxyport= from CVSROOT. * client.h (CVS_PROXY_PORT): default port for a proxy. * client.c (connect_to_pserver): connect to a proxy before doing anything else if one was specified. (auth_server_port_number): return CVSroot_port if set. NEWS entry: * CVS can now tunnel connections through web servers (and hence a firewall) that support the CONNECT command. The syntax augments the connection syntax, for instance :pserver#proxy=www#proxyport=8080#port=2401:, where proxy is the name of the web server proxy, proxyport is the port to connect to on the proxy and port is the remote port to connect to. proxyport defaults to 80 and port defaults to 2401. --- client.c.dist Sat Dec 26 19:37:49 1998 +++ client.c Mon Jan 04 16:31:18 1999 @@ -3727,7 +3727,13 @@ static int auth_server_port_number () { - struct servent *s = getservbyname ("cvspserver", "tcp"); + struct servent *s; + + /* If the user explicitly specified a port use that one. */ + if (CVSroot_port != 0) + return CVSroot_port; + + s = getservbyname ("cvspserver", "tcp"); if (s) return ntohs (s->s_port); @@ -3850,12 +3856,60 @@ error (1, 0, "cannot create socket: %s", SOCK_STRERROR (SOCK_ERRNO)); } port_number = auth_server_port_number (); - hostinfo = init_sockaddr (&client_sai, CVSroot_hostname, port_number); + /* if we have a proxy connect to that instead */ + if (CVSroot_proxy) + { + hostinfo = init_sockaddr (&client_sai, CVSroot_proxy, CVSroot_proxy_port); + } + else + { + hostinfo = init_sockaddr (&client_sai, CVSroot_hostname, port_number); + } + if (connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai)) < 0) - error (1, 0, "connect to %s:%d failed: %s", CVSroot_hostname, - port_number, SOCK_STRERROR (SOCK_ERRNO)); + error (1, 0, "connect to %s:%d failed: %s", + CVSroot_proxy ? CVSroot_proxy :CVSroot_hostname, + CVSroot_proxy ? CVSroot_proxy_port : port_number, + SOCK_STRERROR (SOCK_ERRNO)); + /* if we have proxy then connect to the proxy first */ + if (CVSroot_proxy) + { +#define CONNECT_STRING "CONNECT %s:%d HTTP/1.0\r\n\r\n" + /* Send a "CONNECT" command to proxy: */ + char* read_buf; + int codenum, count; + /* 4 characters for port covered by the length of %s & %d */ + char* write_buf = xmalloc (strlen (CONNECT_STRING) + + strlen (CVSroot_hostname) + 1); + sprintf (write_buf, CONNECT_STRING, + CVSroot_hostname, port_number); + send (sock, write_buf, strlen (write_buf), 0); + + /* Wait for HTTP status code, bail out if you don't get back a 2xx code.*/ + count = recv_line (sock, &read_buf); + sscanf (read_buf, "%s %d", write_buf, &codenum); + + if ((codenum / 100) != 2) + error (1, 0, "proxy server %s:%d does not support http tunnelling", + CVSroot_proxy, CVSroot_proxy_port); + free (read_buf); + free (write_buf); + + /* Skip through remaining part of MIME header, recv_line + consumes the trailing \n */ + while(recv_line (sock, &read_buf) > 0) + { + if (read_buf[0] == '\r' || read_buf[0] == 0) + { + free (read_buf); + break; + } + free (read_buf); + } + } + /* Run the authorization mini-protocol before anything else. */ if (do_gssapi) { --- root.c.dist Sat Dec 26 19:38:00 1998 +++ root.c Sat Dec 26 22:22:15 1998 @@ -291,6 +291,9 @@ char *CVSroot_username; /* the username or NULL if method == local */ char *CVSroot_hostname; /* the hostname or NULL if method == local */ char *CVSroot_directory; /* the directory name */ +char *CVSroot_proxy = NULL; /* the proxy to tunnel through */ +int CVSroot_port = 0; /* the remote port to use */ +int CVSroot_proxy_port = CVS_PROXY_PORT; /* the proxy port to tunnel through */ int parse_cvsroot (CVSroot) @@ -313,6 +316,7 @@ if ((*cvsroot_copy == ':')) { char *method = ++cvsroot_copy; + int have_semicolon; /* Access method specified, as in * "cvs -d :pserver:user@host:/path", @@ -323,11 +327,13 @@ * rest of it. */ - if (! (p = strchr (method, ':'))) + p = strpbrk (method, ":;#"); + if (p == NULL) { error (0, 0, "bad CVSroot: %s", CVSroot); return 1; } + have_semicolon = (*p == ';' || *p == '#'); *p = '\0'; cvsroot_copy = ++p; @@ -351,6 +357,59 @@ { error (0, 0, "unknown method in CVSroot: %s", CVSroot); return 1; + } + + while (have_semicolon) + { + /* More elaborate implementation would allow multiple + semicolons, for example: + + :server;rsh=34;command=cvs-1.6: + + we will allow + :server;port=22;proxy=www-proxy;proxyport=8080: + + we will also allow # as well as ; as a separator to + avoid having to quote the root in a shell. + */ + /* FIXME: lots of error conditions should be better handled, + e.g. garbage after the number or no valid number. + + Would be nice to have testcases for some of these cases + including the error cases. */ + p = strpbrk (cvsroot_copy, ":;#"); + if (p == NULL) + error (1, 0, "[semi]colon missing in %s", CVSroot); + + /* pick up more options if we have them */ + have_semicolon = (*p == ';' || *p == '#'); + *p = '\0'; + + if (strncmp (cvsroot_copy, "port=", 5) == 0) + { + if (cvsroot_copy [5] == '\0') + error (1, 0, "no port specified in CVSROOT: %s", + cvsroot_copy); + CVSroot_port = atoi (cvsroot_copy + 5); + } + else if (strncmp (cvsroot_copy, "proxy=", 6) == 0) + { + CVSroot_proxy = xstrdup (cvsroot_copy + 6); + if (*CVSroot_proxy == '\0') + error (1, 0, "no proxy specified in CVSROOT: %s", + cvsroot_copy); + } + else if (strncmp (cvsroot_copy, "proxyport=", 10) == 0) + { + if (cvsroot_copy [10] == '\0') + error (1, 0, "no proxy port specified in CVSROOT: %s", + cvsroot_copy); + CVSroot_proxy_port = atoi (cvsroot_copy + 10); + } + else + error (1, 0, "invalid semicolon expression in CVSROOT: %s", + cvsroot_copy); + cvsroot_copy = ++p; } } else --- cvs.h.dist Sat Dec 26 21:20:37 1998 +++ cvs.h Sat Dec 26 22:08:00 1998 @@ -383,6 +383,12 @@ extern char *CVSroot_username; /* the username or NULL if method == local */ extern char *CVSroot_hostname; /* the hostname or NULL if method == local */ extern char *CVSroot_directory; /* the directory name */ +/* + * Options for non-standard ports etc + */ +extern int CVSroot_port; /* port on the remote cvs server */ +extern int CVSroot_proxy_port; /* the port on the proxy through which to tunnel */ +extern char* CVSroot_proxy; /* proxy to tunnel through */ /* These variables keep track of all of the CVSROOT directories that have been seen by the client and the current one of those selected. */ --- client.h.dist Sat Dec 26 19:37:41 1998 +++ client.h Sat Dec 26 21:35:22 1998 @@ -63,6 +63,9 @@ # ifndef CVS_AUTH_PORT # define CVS_AUTH_PORT 2401 # endif /* CVS_AUTH_PORT */ +# ifndef CVS_PROXY_PORT +# define CVS_PROXY_PORT 80 +# endif /* CVS_PROXY_PORT */ #endif /* AUTH_CLIENT_SUPPORT */ #if defined (AUTH_SERVER_SUPPORT) || (defined (SERVER_SUPPORT) && defined (HAVE_GSSAPI))