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. --- client.c.dist Sat Dec 26 19:37:49 1998 +++ client.c Sun Dec 27 14:58:12 1998 @@ -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,61 @@ 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 */ + while(recv_line (sock, &read_buf) > 0) + { + if ((read_buf[0] == '\r' && read_buf[1] == '\n') + || + read_buf[0] == '\n') + { + 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))