Add proxy support (based on Pál Dorogi's version)
authorDavid Woodhouse <David.Woodhouse@intel.com>
Fri, 1 Jan 2010 22:09:25 +0000 (22:09 +0000)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Fri, 1 Jan 2010 22:09:25 +0000 (22:09 +0000)
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
http.c
main.c
openconnect.8
openconnect.h
openconnect.html
ssl.c

diff --git a/http.c b/http.c
index 8e8e581..c784ddc 100644 (file)
--- a/http.c
+++ b/http.c
@@ -708,3 +708,94 @@ char *openconnect_create_useragent(char *base)
 
        return uagent;
 }
+
+static int proxy_gets(int fd, char *buf, size_t len)
+{
+       int i = 0;
+       int ret;
+
+       if (len < 2)
+               return -EINVAL;
+
+       while ( (ret = read(fd, buf + i, 1)) == 1) {
+               if (buf[i] == '\n') {
+                       buf[i] = 0;
+                       if (i && buf[i-1] == '\r') {
+                               buf[i-1] = 0;
+                               i--;
+                       }
+                       return i;
+               }
+               i++;
+
+               if (i >= len - 1) {
+                       buf[i] = 0;
+                       return i;
+               }
+       }
+       if (ret < 0)
+               ret = -errno;
+
+       buf[i] = 0;
+       return i ?: ret;
+}
+
+
+int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
+{
+       char buf[MAX_BUF_LEN];
+       int count, buflen, result;
+
+       sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
+       sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
+       sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
+       sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
+       sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
+       sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
+       sprintf(buf + strlen(buf), "\r\n");
+
+       vpninfo->progress(vpninfo, PRG_INFO, "Requesting proxy connection to %s:%d\n",
+                         vpninfo->hostname, vpninfo->port);
+
+       buflen = strlen(buf);
+       for (count = 0; count < buflen; ) {
+               int i = write(ssl_sock, buf + count, buflen - count);
+               if (i < 0) {
+                       i = -errno;
+                       vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
+                                         strerror(errno));
+                       return i;
+               }
+               count += i;
+       }
+
+       if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
+               vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
+               return -EIO;
+       }
+
+       if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
+           buf[8] != ' ' || !(result = atoi(buf+9))) {
+               vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
+                                 buf);
+               return -EINVAL;
+       }
+
+       if (result != 200) {
+               vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
+                                 buf);
+               return -EIO;
+       }
+
+       while ((count = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
+               if (count < 0) {
+                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
+                       return -EIO;
+               }
+               vpninfo->progress(vpninfo, PRG_ERR,
+                                 "Unexpected continuation line after CONNECT response: '%s'\n",
+                                 buf);
+       }
+
+       return 0;
+}
diff --git a/main.c b/main.c
index a170ece..973b602 100644 (file)
--- a/main.c
+++ b/main.c
@@ -68,6 +68,7 @@ static struct option long_options[] = {
        {"syslog", 0, 0, 'l'},
        {"key-type", 1, 0, 'K'},
        {"key-password", 1, 0, 'p'},
+       {"proxy", 1, 0, 'P'},
        {"user", 1, 0, 'u'},
        {"verbose", 0, 0, 'v'},
        {"version", 0, 0, 'V'},
@@ -89,6 +90,7 @@ static struct option long_options[] = {
        {"useragent", 1, 0, 0x03},
        {"csd-user", 1, 0, 0x04},
        {"disable-ipv6", 0, 0, 0x05},
+       {"no-proxy", 0, 0, 0x06},
        {NULL, 0, 0, 0},
 };
 
@@ -109,10 +111,12 @@ void usage(void)
        printf("  -i, --interface=IFNAME          Use IFNAME for tunnel interface\n");
        printf("  -l, --syslog                    Use syslog for progress messages\n");
        printf("  -U, --setuid=USER               Drop privileges after connecting\n");
-       printf("      --csd-user=USER           Drop privileges during CSD execution\n");
+       printf("      --csd-user=USER             Drop privileges during CSD execution\n");
        printf("  -m, --mtu=MTU                   Request MTU from server\n");
        printf("  -p, --key-password=PASS         Set key passphrase or TPM SRK PIN\n");
        printf("      --key-password-from-fsid    Key passphrase is fsid of file system\n");
+       printf("  -P, --proxy=URL                 Set proxy server\n");
+       printf("      --no-proxy                  Disable proxy\n");
        printf("  -q, --quiet                     Less output\n");
        printf("  -Q, --queue-len=LEN             Set packet queue limit to LEN pkts\n");
        printf("  -s, --script=SCRIPT             Use vpnc-compatible config script\n");
@@ -202,7 +206,7 @@ int main(int argc, char **argv)
        else
                vpninfo->localname = "localhost";
 
-       while ((opt = getopt_long(argc, argv, "bC:c:Ddg:hi:k:K:lp:Q:qSs:U:u:Vvx:",
+       while ((opt = getopt_long(argc, argv, "bC:c:Ddg:hi:k:K:lpP:Q:qSs:U:u:Vvx:",
                                  long_options, NULL))) {
                if (opt < 0)
                        break;
@@ -298,6 +302,22 @@ int main(int argc, char **argv)
                case 'p':
                        vpninfo->cert_password = optarg;
                        break;
+               case 'P': {
+                       char *url = strdup(optarg);
+                       char *scheme;
+                       parse_url(url, &scheme, &vpninfo->proxy, &vpninfo->proxy_port, NULL);
+                       if (scheme && strcmp(scheme, "http")) {
+                               fprintf(stderr, "Non-http proxy not supported\n");
+                               exit(1);
+                       }
+                       free(scheme);
+                       free(url);
+                       vpninfo->dtls_attempt_period = 0;
+                       break;
+               }
+               case 0x06:
+                       free(vpninfo->proxy);
+                       vpninfo->proxy = NULL;
                case 's':
                        vpninfo->vpnc_script = optarg;
                        break;
index 39b4250..c878692 100644 (file)
@@ -62,6 +62,13 @@ openconnect \- Connect to Cisco AnyConnect VPN
 .I PASS
 ]
 [
+.B -P,--proxy
+.I PROXY
+]
+[
+.B --no-proxy
+]
+[
 .B --key-password-from-fsid
 ]
 [
@@ -221,6 +228,12 @@ from server
 .B -p,--key-password=PASS
 Provide passphrase for certificate file, or SRK (System Root Key) PIN for TPM
 .TP
+.B -P,--proxy=PROXY
+Use HTTP proxy for connection
+.TP
+.B --no-proxy
+Disable use of HTTP proxy
+.TP
 .B --key-password-from-fsid
 Passphrase for certificate file is automatically generated from the fsid of
 the file system on which it is stored
index e6e45dd..2ce13c6 100644 (file)
@@ -143,6 +143,9 @@ struct openconnect_info {
        char sid_tokencode[9];
        char sid_nexttokencode[9];
 
+       char *proxy;
+       int proxy_port;
+
        const char *localname;
        char *hostname;
        int port;
@@ -320,6 +323,8 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response,
 /* http.c */
 int openconnect_obtain_cookie(struct openconnect_info *vpninfo);
 char *openconnect_create_useragent(char *base);
+int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock);
+int parse_url(char *url, char **res_proto, char **res_host, int *res_port, char **res_path);
 
 /* ssl_ui.c */
 int set_openssl_ui(void);
index 1b94f96..49323b1 100644 (file)
@@ -173,6 +173,7 @@ For full changelog entries including the latest development, see
 <UL>
   <LI><B>OpenConnect HEAD</B><BR>
      <UL>
+       <LI>Support connection through HTTP proxy.</LI>
        <LI>Handle HTTP redirection with port numbers.</LI>
        <LI>Handle HTTP redirection with IPv6 literal addresses.</LI>
      </UL><BR>
@@ -386,6 +387,6 @@ An <TT>openconnect</TT> <A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/ports/sec
 <hr>
 <address>David Woodhouse &lt;<A HREF="mailto:dwmw2@infradead.org">dwmw2@infradead.org</A>&gt;</address>
 <!-- hhmts start -->
-Last modified: Wed Dec 23 22:32:16 GMT 2009
+Last modified: Fri Jan  1 22:08:53 GMT 2010
 <!-- hhmts end -->
 </body> </html>
diff --git a/ssl.c b/ssl.c
index f23bfd5..83bf93c 100644 (file)
--- a/ssl.c
+++ b/ssl.c
@@ -484,16 +484,17 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
                ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM, IPPROTO_IP);
                if (ssl_sock < 0) {
                reconn_err:
-                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to reconnect to host %s\n", vpninfo->hostname);
+                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to reconnect to %s %s\n",
+                                         vpninfo->proxy?"proxy":"host",
+                                         vpninfo->proxy?:vpninfo->hostname);
                        return -EINVAL;
                }
                if (connect(ssl_sock, vpninfo->peer_addr, vpninfo->peer_addrlen))
                        goto reconn_err;
-
-               
                
        } else {
                struct addrinfo hints, *result, *rp;
+               char *hostname;
                char port[6];
 
                memset(&hints, 0, sizeof(struct addrinfo));
@@ -505,20 +506,37 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
                hints.ai_addr = NULL;
                hints.ai_next = NULL;
 
-               /* We do this because it's easier than passing NULL as the 
-                  port and then having to fill it in differently for IPv4
-                  and IPv6 destinations later. */
-               snprintf(port, 5, "%d", vpninfo->port);
-               err = getaddrinfo(vpninfo->hostname, port, &hints, &result);
+               /* The 'port' variable is a string because it's easier
+                  this way than if we pass NULL to getaddrinfo() and
+                  then try to fill in the numeric value into
+                  different types of returned sockaddr_in{6,}. */
+               if (vpninfo->proxy) {
+                       hostname = vpninfo->proxy;
+                       snprintf(port, 5, "%d", vpninfo->proxy_port);
+               } else {
+                       hostname = vpninfo->hostname;
+                       snprintf(port, 5, "%d", vpninfo->port);
+               }
+
+               err = getaddrinfo(hostname, port, &hints, &result);
                if (err) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "getaddrinfo failed: %s\n", gai_strerror(err));
+                       vpninfo->progress(vpninfo, PRG_ERR, "getaddrinfo failed: %s\n",
+                                         gai_strerror(err));
                        return -EINVAL;
                }
 
-               vpninfo->progress(vpninfo, PRG_INFO,
-                                 "Attempting to connect to %s\n", vpninfo->hostname);
-
                for (rp = result; rp ; rp = rp->ai_next) {
+                       char host[80];
+
+                       if (!getnameinfo(rp->ai_addr, rp->ai_addrlen, host,
+                                        sizeof(host), NULL, 0, NI_NUMERICHOST))
+                               vpninfo->progress(vpninfo, PRG_INFO,
+                                                 "Attempting to connect to %s%s%s:%s\n",
+                                                 rp->ai_family == AF_INET6?"[":"",
+                                                 host,
+                                                 rp->ai_family == AF_INET6?"]":"",
+                                                 port);
+                       
                        ssl_sock = socket(rp->ai_family, rp->ai_socktype,
                                          rp->ai_protocol);
                        if (ssl_sock < 0)
@@ -542,12 +560,20 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
                freeaddrinfo(result);
                
                if (ssl_sock < 0) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to connect to host %s\n", vpninfo->hostname);
+                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to connect to host %s\n", hostname);
                        return -EINVAL;
                }
        }
        fcntl(ssl_sock, F_SETFD, FD_CLOEXEC);
 
+       if (vpninfo->proxy) {
+               err = process_http_proxy(vpninfo, ssl_sock);
+               if (err) {
+                       close(ssl_sock);
+                       return err;
+               }
+       }
+
        ssl3_method = TLSv1_client_method();
        if (!vpninfo->https_ctx) {
                vpninfo->https_ctx = SSL_CTX_new(ssl3_method);