1 /*
2 * OpenConnect (SSL + DTLS) VPN client
3 *
4 * Copyright © 2008-2015 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
6 *
7 * Author: David Woodhouse <dwmw2@infradead.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 */
18
19 #include <config.h>
20
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <time.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <stdarg.h>
30
31 #include <libxml/uri.h>
32
33 #include "openconnect-internal.h"
34
35 static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len);
36 static int proxy_read(struct openconnect_info *vpninfo, char *buf, size_t len);
37
38 #define BUF_CHUNK_SIZE 4096
39
buf_alloc(void)40 struct oc_text_buf *buf_alloc(void)
41 {
42 return calloc(1, sizeof(struct oc_text_buf));
43 }
44
buf_append_urlencoded(struct oc_text_buf * buf,const char * str)45 void buf_append_urlencoded(struct oc_text_buf *buf, const char *str)
46 {
47 while (str && *str) {
48 unsigned char c = *str;
49 if (c < 0x80 && (isalnum((int)(c)) || c=='-' || c=='_' || c=='.' || c=='~'))
50 buf_append_bytes(buf, str, 1);
51 else
52 buf_append(buf, "%%%02x", c);
53
54 str++;
55 }
56 }
57
buf_append_xmlescaped(struct oc_text_buf * buf,const char * str)58 void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str)
59 {
60 while (str && *str) {
61 unsigned char c = *str;
62 if (c=='<' || c=='>' || c=='&' || c=='"' || c=='\'')
63 buf_append(buf, "&#x%02x;", c);
64 else
65 buf_append_bytes(buf, str, 1);
66
67 str++;
68 }
69 }
70
buf_append_hex(struct oc_text_buf * buf,const void * str,unsigned len)71 void buf_append_hex(struct oc_text_buf *buf, const void *str, unsigned len)
72 {
73 const unsigned char *data = str;
74 unsigned i;
75
76 for (i = 0; i < len; i++)
77 buf_append(buf, "%02x", (unsigned)data[i]);
78 }
79
buf_truncate(struct oc_text_buf * buf)80 void buf_truncate(struct oc_text_buf *buf)
81 {
82 if (!buf)
83 return;
84
85 if (buf->data)
86 memset(buf->data, 0, buf->pos);
87
88 buf->pos = 0;
89 }
90
buf_ensure_space(struct oc_text_buf * buf,int len)91 int buf_ensure_space(struct oc_text_buf *buf, int len)
92 {
93 unsigned int new_buf_len;
94
95 if (!buf)
96 return -ENOMEM;
97
98 new_buf_len = (buf->pos + len + BUF_CHUNK_SIZE - 1) & ~(BUF_CHUNK_SIZE - 1);
99
100 if (new_buf_len <= buf->buf_len)
101 return 0;
102
103 if (new_buf_len > INT_MAX) {
104 buf->error = -E2BIG;
105 return buf->error;
106 } else {
107 realloc_inplace(buf->data, new_buf_len);
108 if (!buf->data)
109 buf->error = -ENOMEM;
110 else
111 buf->buf_len = new_buf_len;
112 }
113 return buf->error;
114 }
115
116 void __attribute__ ((format (printf, 2, 3)))
buf_append(struct oc_text_buf * buf,const char * fmt,...)117 buf_append(struct oc_text_buf *buf, const char *fmt, ...)
118 {
119 va_list ap;
120
121 if (!buf || buf->error)
122 return;
123
124 if (buf_ensure_space(buf, 1))
125 return;
126
127 while (1) {
128 int max_len = buf->buf_len - buf->pos, ret;
129
130 va_start(ap, fmt);
131 ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
132 va_end(ap);
133 if (ret < 0) {
134 buf->error = -EIO;
135 break;
136 } else if (ret < max_len) {
137 buf->pos += ret;
138 break;
139 } else if (buf_ensure_space(buf, ret))
140 break;
141 }
142 }
143
buf_append_bytes(struct oc_text_buf * buf,const void * bytes,int len)144 void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len)
145 {
146 if (!buf || buf->error)
147 return;
148
149 if (buf_ensure_space(buf, len + 1))
150 return;
151
152 memcpy(buf->data + buf->pos, bytes, len);
153 buf->pos += len;
154 buf->data[buf->pos] = 0;
155 }
156
buf_append_from_utf16le(struct oc_text_buf * buf,const void * _utf16)157 void buf_append_from_utf16le(struct oc_text_buf *buf, const void *_utf16)
158 {
159 const unsigned char *utf16 = _utf16;
160 unsigned char utf8[4];
161 int c;
162
163 if (!utf16)
164 return;
165
166 while (utf16[0] || utf16[1]) {
167 if ((utf16[1] & 0xfc) == 0xd8 && (utf16[3] & 0xfc) == 0xdc) {
168 c = ((load_le16(utf16) & 0x3ff) << 10)|
169 (load_le16(utf16 + 2) & 0x3ff);
170 c += 0x10000;
171 utf16 += 4;
172 } else {
173 c = load_le16(utf16);
174 utf16 += 2;
175 }
176
177 if (c < 0x80) {
178 utf8[0] = c;
179 buf_append_bytes(buf, utf8, 1);
180 } else if (c < 0x800) {
181 utf8[0] = 0xc0 | (c >> 6);
182 utf8[1] = 0x80 | (c & 0x3f);
183 buf_append_bytes(buf, utf8, 2);
184 } else if (c < 0x10000) {
185 utf8[0] = 0xe0 | (c >> 12);
186 utf8[1] = 0x80 | ((c >> 6) & 0x3f);
187 utf8[2] = 0x80 | (c & 0x3f);
188 buf_append_bytes(buf, utf8, 3);
189 } else {
190 utf8[0] = 0xf0 | (c >> 18);
191 utf8[1] = 0x80 | ((c >> 12) & 0x3f);
192 utf8[2] = 0x80 | ((c >> 6) & 0x3f);
193 utf8[3] = 0x80 | (c & 0x3f);
194 buf_append_bytes(buf, utf8, 4);
195 }
196 }
197 utf8[0] = 0;
198 buf_append_bytes(buf, utf8, 1);
199 }
200
get_utf8char(const char ** p)201 int get_utf8char(const char **p)
202 {
203 const char *utf8 = *p;
204 unsigned char c;
205 int utfchar, nr_extra, min;
206
207 c = *(utf8++);
208 if (c < 128) {
209 utfchar = c;
210 nr_extra = 0;
211 min = 0;
212 } else if ((c & 0xe0) == 0xc0) {
213 utfchar = c & 0x1f;
214 nr_extra = 1;
215 min = 0x80;
216 } else if ((c & 0xf0) == 0xe0) {
217 utfchar = c & 0x0f;
218 nr_extra = 2;
219 min = 0x800;
220 } else if ((c & 0xf8) == 0xf0) {
221 utfchar = c & 0x07;
222 nr_extra = 3;
223 min = 0x10000;
224 } else {
225 return -EILSEQ;
226 }
227
228 while (nr_extra--) {
229 c = *(utf8++);
230 if ((c & 0xc0) != 0x80)
231 return -EILSEQ;
232
233 utfchar <<= 6;
234 utfchar |= (c & 0x3f);
235 }
236 if (utfchar > 0x10ffff || utfchar < min)
237 return -EILSEQ;
238
239 *p = utf8;
240 return utfchar;
241 }
242
buf_append_utf16le(struct oc_text_buf * buf,const char * utf8)243 int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8)
244 {
245 int utfchar, len = 0;
246
247 /* Ick. Now I'm implementing my own UTF8 handling too. Perhaps it's
248 time to bite the bullet and start requiring something like glib? */
249 while (*utf8) {
250 utfchar = get_utf8char(&utf8);
251 if (utfchar < 0) {
252 if (buf)
253 buf->error = utfchar;
254 return utfchar;
255 }
256 if (!buf)
257 continue;
258
259 if (utfchar >= 0x10000) {
260 utfchar -= 0x10000;
261 if (buf_ensure_space(buf, 4))
262 return buf_error(buf);
263 store_le16(buf->data + buf->pos, (utfchar >> 10) | 0xd800);
264 store_le16(buf->data + buf->pos + 2, (utfchar & 0x3ff) | 0xdc00);
265 buf->pos += 4;
266 len += 4;
267 } else {
268 if (buf_ensure_space(buf, 2))
269 return buf_error(buf);
270 store_le16(buf->data + buf->pos, utfchar);
271 buf->pos += 2;
272 len += 2;
273 }
274 }
275
276 /* We were only being used for validation */
277 if (!buf)
278 return 0;
279
280 /* Ensure UTF16 is NUL-terminated */
281 if (buf_ensure_space(buf, 2))
282 return buf_error(buf);
283 buf->data[buf->pos] = buf->data[buf->pos + 1] = 0;
284
285 return len;
286 }
287
buf_error(struct oc_text_buf * buf)288 int buf_error(struct oc_text_buf *buf)
289 {
290 return buf ? buf->error : -ENOMEM;
291 }
292
buf_free(struct oc_text_buf * buf)293 int buf_free(struct oc_text_buf *buf)
294 {
295 int error = buf_error(buf);
296
297 if (buf) {
298 buf_truncate(buf);
299 if (buf->data)
300 free(buf->data);
301 free(buf);
302 }
303
304 return error;
305 }
306
307 /*
308 * We didn't really want to have to do this for ourselves -- one might have
309 * thought that it would be available in a library somewhere. But neither
310 * cURL nor Neon have reliable cross-platform ways of either using a cert
311 * from the TPM, or just reading from / writing to a transport which is
312 * provided by their caller.
313 */
314
http_add_cookie(struct openconnect_info * vpninfo,const char * option,const char * value,int replace)315 int http_add_cookie(struct openconnect_info *vpninfo, const char *option,
316 const char *value, int replace)
317 {
318 struct oc_vpn_option *new, **this;
319
320 if (*value) {
321 new = malloc(sizeof(*new));
322 if (!new) {
323 vpn_progress(vpninfo, PRG_ERR,
324 _("No memory for allocating cookies\n"));
325 return -ENOMEM;
326 }
327 new->next = NULL;
328 new->option = strdup(option);
329 new->value = strdup(value);
330 if (!new->option || !new->value) {
331 free(new->option);
332 free(new->value);
333 free(new);
334 return -ENOMEM;
335 }
336 } else {
337 /* Kill cookie; don't replace it */
338 new = NULL;
339 /* This would be meaningless */
340 if (!replace)
341 return -EINVAL;
342 }
343 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
344 if (!strcmp(option, (*this)->option)) {
345 if (!replace) {
346 free(new->value);
347 free(new->option);
348 free(new);
349 return 0;
350 }
351 /* Replace existing cookie */
352 if (new)
353 new->next = (*this)->next;
354 else
355 new = (*this)->next;
356
357 free((*this)->option);
358 free((*this)->value);
359 free(*this);
360 *this = new;
361 break;
362 }
363 }
364 if (new && !*this) {
365 *this = new;
366 new->next = NULL;
367 }
368 return 0;
369 }
370
371 /* Read one HTTP header line into hdrbuf, potentially allowing for
372 * continuation lines. Will never leave a character in 'nextchar' when
373 * an empty line (signifying end of headers) is received. Will only
374 * return success when hdrbuf is valid. */
read_http_header(struct openconnect_info * vpninfo,char * nextchar,struct oc_text_buf * hdrbuf,int allow_cont)375 static int read_http_header(struct openconnect_info *vpninfo, char *nextchar,
376 struct oc_text_buf *hdrbuf, int allow_cont)
377 {
378 int eol = 0;
379 int ret;
380 char c;
381
382
383 buf_truncate(hdrbuf);
384
385 c = *nextchar;
386 if (c) {
387 *nextchar = 0;
388 goto skip_first;
389 }
390
391 while (1) {
392 ret = vpninfo->ssl_read(vpninfo, &c, 1);
393 if (ret < 0)
394 return ret;
395 if (ret != 1)
396 return -EINVAL;
397
398 /* If we were looking for a continuation line and didn't get it,
399 * stash the character we *did* get into *nextchar for next time. */
400 if (eol && c != ' ' && c != '\t') {
401 *nextchar = c;
402 return buf_error(hdrbuf);
403 }
404 eol = 0;
405
406 skip_first:
407 if (c == '\n') {
408 if (!buf_error(hdrbuf) && hdrbuf->pos &&
409 hdrbuf->data[hdrbuf->pos - 1] == '\r') {
410 hdrbuf->pos--;
411 hdrbuf->data[hdrbuf->pos] = 0;
412 }
413
414 /* For a non-empty header line, see if there's a continuation */
415 if (allow_cont && hdrbuf->pos) {
416 eol = 1;
417 continue;
418 }
419
420 return buf_error(hdrbuf);
421 }
422 buf_append_bytes(hdrbuf, &c, 1);
423 }
424 return buf_error(hdrbuf);
425 }
426
427 #define BODY_HTTP10 -1
428 #define BODY_CHUNKED -2
429
process_http_response(struct openconnect_info * vpninfo,int connect,int (* header_cb)(struct openconnect_info *,char *,char *),struct oc_text_buf * body)430 int process_http_response(struct openconnect_info *vpninfo, int connect,
431 int (*header_cb)(struct openconnect_info *, char *, char *),
432 struct oc_text_buf *body)
433 {
434 struct oc_text_buf *hdrbuf = buf_alloc();
435 char nextchar = 0;
436 int bodylen = BODY_HTTP10;
437 int closeconn = 0;
438 int result;
439 int ret = -EINVAL;
440 int i;
441
442 buf_truncate(body);
443
444 /* Ensure it has *something* in it, so that we can dereference hdrbuf->data
445 * later without checking (for anything except buf_error(hdrbuf), which is
446 * what read_http_header() uses for its return code anyway). */
447 buf_append_bytes(hdrbuf, "\0", 1);
448 cont:
449 ret = read_http_header(vpninfo, &nextchar, hdrbuf, 0);
450 if (ret) {
451 vpn_progress(vpninfo, PRG_ERR, _("Error reading HTTP response: %s\n"),
452 strerror(-ret));
453 goto err;
454 }
455
456 if (!strncmp(hdrbuf->data, "HTTP/1.0 ", 9))
457 closeconn = 1;
458
459 if ((!closeconn && strncmp(hdrbuf->data, "HTTP/1.1 ", 9)) ||
460 !(result = atoi(hdrbuf->data + 9))) {
461 vpn_progress(vpninfo, PRG_ERR,
462 _("Failed to parse HTTP response '%s'\n"), hdrbuf->data);
463 ret = -EINVAL;
464 goto err;
465 }
466
467 vpn_progress(vpninfo, (result == 200 || result == 407) ? PRG_DEBUG : PRG_INFO,
468 _("Got HTTP response: %s\n"), hdrbuf->data);
469
470 /* Eat headers... */
471 while (1) {
472 char *colon;
473 char *hdrline;
474
475 ret = read_http_header(vpninfo, &nextchar, hdrbuf, 1);
476 if (ret) {
477 vpn_progress(vpninfo, PRG_ERR, _("Error reading HTTP response: %s\n"),
478 strerror(-ret));
479 goto err;
480 }
481 /* Default error case */
482 ret = -EINVAL;
483
484 /* Empty line ends headers */
485 if (!hdrbuf->pos)
486 break;
487
488 hdrline = hdrbuf->data;
489
490 colon = strchr(hdrline, ':');
491 if (!colon) {
492 vpn_progress(vpninfo, PRG_ERR,
493 _("Ignoring unknown HTTP response line '%s'\n"), hdrline);
494 continue;
495 }
496 *(colon++) = 0;
497 if (*colon == ' ')
498 colon++;
499
500 /* Handle Set-Cookie first so that we can avoid printing the
501 webvpn cookie in the verbose debug output */
502 if (!strcasecmp(hdrline, "Set-Cookie")) {
503 char *semicolon = strchr(colon, ';');
504 const char *print_equals;
505 char *equals = strchr(colon, '=');
506
507 if (semicolon)
508 *semicolon = 0;
509
510 if (!equals) {
511 vpn_progress(vpninfo, PRG_ERR,
512 _("Invalid cookie offered: %s\n"), hdrline);
513 ret = -EINVAL;
514 goto err;
515 }
516 *(equals++) = 0;
517
518 print_equals = equals;
519 /* Don't print the webvpn cookie unless it's empty; we don't
520 want people posting it in public with debugging output */
521 if (!strcmp(colon, "webvpn") && *equals)
522 print_equals = _("<elided>");
523 vpn_progress(vpninfo, PRG_DEBUG, "%s: %s=%s%s%s\n",
524 hdrline, colon, print_equals, semicolon ? ";" : "",
525 semicolon ? (semicolon+1) : "");
526
527 /* The server tends to ask for the username and password as
528 usual, even if we've already failed because it didn't like
529 our cert. Thankfully it does give us this hint... */
530 if (!strcmp(colon, "ClientCertAuthFailed"))
531 vpn_progress(vpninfo, PRG_ERR,
532 _("SSL certificate authentication failed\n"));
533
534 ret = http_add_cookie(vpninfo, colon, equals, 1);
535 if (ret)
536 goto err;
537 } else {
538 vpn_progress(vpninfo, PRG_DEBUG, "%s: %s\n", hdrline, colon);
539 }
540
541 if (!strcasecmp(hdrline, "Connection")) {
542 if (!strcasecmp(colon, "Close"))
543 closeconn = 1;
544 #if 0
545 /* This might seem reasonable, but in fact it breaks
546 certificate authentication with some servers. If
547 they give an HTTP/1.0 response, even if they
548 explicitly give a Connection: Keep-Alive header,
549 just close the connection. */
550 else if (!strcasecmp(colon, "Keep-Alive"))
551 closeconn = 0;
552 #endif
553 }
554 if (!strcasecmp(hdrline, "Location")) {
555 vpninfo->redirect_url = strdup(colon);
556 if (!vpninfo->redirect_url) {
557 ret = -ENOMEM;
558 goto err;
559 }
560 }
561 if (!strcasecmp(hdrline, "Content-Length")) {
562 bodylen = atoi(colon);
563 if (bodylen < 0) {
564 vpn_progress(vpninfo, PRG_ERR,
565 _("Response body has negative size (%d)\n"),
566 bodylen);
567 ret = -EINVAL;
568 goto err;
569 }
570 }
571 if (!strcasecmp(hdrline, "Transfer-Encoding")) {
572 if (!strcasecmp(colon, "chunked"))
573 bodylen = BODY_CHUNKED;
574 else {
575 vpn_progress(vpninfo, PRG_ERR,
576 _("Unknown Transfer-Encoding: %s\n"),
577 colon);
578 ret = -EINVAL;
579 goto err;
580 }
581 }
582 if (header_cb)
583 header_cb(vpninfo, hdrline, colon);
584 }
585
586 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
587 if (result == 100)
588 goto cont;
589
590 /* On successful CONNECT or upgrade, there is no body. Return success */
591 if (connect && (result == 200 || result == 101)) {
592 buf_free(hdrbuf);
593 return result;
594 }
595
596 /* Now the body, if there is one */
597 vpn_progress(vpninfo, PRG_DEBUG, _("HTTP body %s (%d)\n"),
598 bodylen == BODY_HTTP10 ? "http 1.0" :
599 bodylen == BODY_CHUNKED ? "chunked" : "length: ",
600 bodylen);
601
602 /* If we were given Content-Length, it's nice and easy... */
603 if (bodylen > 0) {
604 if (buf_ensure_space(body, bodylen + 1)) {
605 ret = buf_error(body);
606 goto err;
607 }
608
609 while (body->pos < bodylen) {
610 i = vpninfo->ssl_read(vpninfo, body->data + body->pos, bodylen - body->pos);
611 if (i < 0) {
612 vpn_progress(vpninfo, PRG_ERR,
613 _("Error reading HTTP response body\n"));
614 ret = i;
615 goto err;
616 }
617 body->pos += i;
618 }
619 } else if (bodylen == BODY_CHUNKED) {
620 char clen_buf[16];
621 /* ... else, chunked */
622 while ((i = vpninfo->ssl_gets(vpninfo, clen_buf, sizeof(clen_buf)))) {
623 int lastchunk = 0;
624 long chunklen;
625
626 if (i < 0) {
627 vpn_progress(vpninfo, PRG_ERR,
628 _("Error fetching chunk header\n"));
629 ret = i;
630 goto err;
631 }
632 chunklen = strtol(clen_buf, NULL, 16);
633 if (!chunklen) {
634 lastchunk = 1;
635 goto skip;
636 }
637 if (chunklen < 0) {
638 vpn_progress(vpninfo, PRG_ERR,
639 _("HTTP chunk length is negative (%ld)\n"), chunklen);
640 ret = -EINVAL;
641 goto err;
642 }
643 if (chunklen >= INT_MAX) {
644 vpn_progress(vpninfo, PRG_ERR,
645 _("HTTP chunk length is too large (%ld)\n"), chunklen);
646 ret = -EINVAL;
647 goto err;
648 }
649 if (buf_ensure_space(body, chunklen + 1)) {
650 ret = buf_error(body);
651 goto err;
652 }
653 while (chunklen) {
654 i = vpninfo->ssl_read(vpninfo, body->data + body->pos, chunklen);
655 if (i < 0) {
656 vpn_progress(vpninfo, PRG_ERR,
657 _("Error reading HTTP response body\n"));
658 ret = i;
659 goto err;
660 }
661 chunklen -= i;
662 body->pos += i;
663 }
664 skip:
665 if ((i = vpninfo->ssl_gets(vpninfo, clen_buf, sizeof(clen_buf)))) {
666 if (i < 0) {
667 vpn_progress(vpninfo, PRG_ERR,
668 _("Error fetching HTTP response body\n"));
669 ret = i;
670 } else {
671 vpn_progress(vpninfo, PRG_ERR,
672 _("Error in chunked decoding. Expected '', got: '%s'"),
673 clen_buf);
674 ret = -EINVAL;
675 }
676 goto err;
677 }
678
679 if (lastchunk)
680 break;
681 }
682 } else if (bodylen == BODY_HTTP10) {
683 if (!closeconn) {
684 vpn_progress(vpninfo, PRG_ERR,
685 _("Cannot receive HTTP 1.0 body without closing connection\n"));
686 openconnect_close_https(vpninfo, 0);
687 return -EINVAL;
688 }
689
690 /* HTTP 1.0 response. Just eat all we can in 4KiB chunks */
691 while (1) {
692 if (buf_ensure_space(body, 4096 + 1)) {
693 ret = buf_error(body);
694 goto err;
695 }
696 i = vpninfo->ssl_read(vpninfo, body->data + body->pos, 4096);
697 if (i < 0) {
698 /* Error */
699 ret = i;
700 goto err;
701 } else if (!i)
702 break;
703
704 /* Got more data */
705 body->pos += i;
706 }
707 }
708
709 body->data[body->pos] = 0;
710 ret = result;
711
712 if (closeconn || vpninfo->no_http_keepalive) {
713 err:
714 openconnect_close_https(vpninfo, 0);
715 }
716 buf_free(hdrbuf);
717 return ret;
718 }
719
internal_parse_url(const char * url,char ** res_proto,char ** res_host,int * res_port,char ** res_path,int default_port)720 int internal_parse_url(const char *url, char **res_proto, char **res_host,
721 int *res_port, char **res_path, int default_port)
722 {
723 const char *orig_host, *orig_path;
724 char *host, *port_str;
725 int port, proto_len = 0;
726
727 orig_host = strstr(url, "://");
728 if (orig_host) {
729 proto_len = orig_host - url;
730 orig_host += 3;
731
732 if (strprefix_match(url, proto_len, "https"))
733 port = 443;
734 else if (strprefix_match(url, proto_len, "http"))
735 port = 80;
736 else if (strprefix_match(url, proto_len, "socks") ||
737 strprefix_match(url, proto_len, "socks4") ||
738 strprefix_match(url, proto_len, "socks5"))
739 port = 1080;
740 else
741 return -EPROTONOSUPPORT;
742 } else {
743 if (default_port) {
744 port = default_port;
745 orig_host = url;
746 } else
747 return -EINVAL;
748 }
749
750 orig_path = strchr(orig_host, '/');
751 if (orig_path) {
752 host = strndup(orig_host, orig_path - orig_host);
753 orig_path++;
754 } else
755 host = strdup(orig_host);
756 if (!host)
757 return -ENOMEM;
758
759 port_str = strrchr(host, ':');
760 if (port_str) {
761 char *end;
762 int new_port = strtol(port_str + 1, &end, 10);
763
764 if (!*end) {
765 *port_str = 0;
766 port = new_port;
767 }
768 }
769
770 if (res_proto)
771 *res_proto = proto_len ? strndup(url, proto_len) : NULL;
772 if (res_host)
773 *res_host = host;
774 else
775 free(host);
776 if (res_port)
777 *res_port = port;
778 if (res_path)
779 *res_path = (orig_path && *orig_path) ? strdup(orig_path) : NULL;
780
781 return 0;
782 }
783
openconnect_clear_cookies(struct openconnect_info * vpninfo)784 void openconnect_clear_cookies(struct openconnect_info *vpninfo)
785 {
786 struct oc_vpn_option *opt, *next;
787
788 for (opt = vpninfo->cookies; opt; opt = next) {
789 next = opt->next;
790
791 free(opt->option);
792 free(opt->value);
793 free(opt);
794 }
795 vpninfo->cookies = NULL;
796 }
797
798 /* Return value:
799 * < 0, on error
800 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
801 */
handle_redirect(struct openconnect_info * vpninfo)802 int handle_redirect(struct openconnect_info *vpninfo)
803 {
804 vpninfo->redirect_type = REDIR_TYPE_LOCAL;
805
806 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
807 /* New host. Tear down the existing connection and make a new one */
808 char *host;
809 int port;
810 int ret;
811
812 free(vpninfo->urlpath);
813 vpninfo->urlpath = NULL;
814
815 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
816 if (ret) {
817 vpn_progress(vpninfo, PRG_ERR,
818 _("Failed to parse redirected URL '%s': %s\n"),
819 vpninfo->redirect_url, strerror(-ret));
820 free(vpninfo->redirect_url);
821 vpninfo->redirect_url = NULL;
822 return ret;
823 }
824
825 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
826 openconnect_set_hostname(vpninfo, host);
827 vpninfo->port = port;
828
829 /* Kill the existing connection, and a new one will happen */
830 openconnect_close_https(vpninfo, 0);
831 openconnect_clear_cookies(vpninfo);
832 vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
833 }
834 free(host);
835
836 free(vpninfo->redirect_url);
837 vpninfo->redirect_url = NULL;
838
839 return 0;
840 } else if (strstr(vpninfo->redirect_url, "://")) {
841 vpn_progress(vpninfo, PRG_ERR,
842 _("Cannot follow redirection to non-https URL '%s'\n"),
843 vpninfo->redirect_url);
844 free(vpninfo->redirect_url);
845 vpninfo->redirect_url = NULL;
846 return -EINVAL;
847 } else if (vpninfo->redirect_url[0] == '/') {
848 /* Absolute redirect within same host */
849 free(vpninfo->urlpath);
850 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
851 free(vpninfo->redirect_url);
852 vpninfo->redirect_url = NULL;
853 return 0;
854 } else {
855 char *lastslash = NULL;
856 if (vpninfo->urlpath)
857 lastslash = strrchr(vpninfo->urlpath, '/');
858 if (!lastslash) {
859 free(vpninfo->urlpath);
860 vpninfo->urlpath = vpninfo->redirect_url;
861 vpninfo->redirect_url = NULL;
862 } else {
863 char *oldurl = vpninfo->urlpath;
864 *lastslash = 0;
865 vpninfo->urlpath = NULL;
866 if (asprintf(&vpninfo->urlpath, "%s/%s",
867 oldurl, vpninfo->redirect_url) == -1) {
868 int err = -errno;
869 vpn_progress(vpninfo, PRG_ERR,
870 _("Allocating new path for relative redirect failed: %s\n"),
871 strerror(-err));
872 return err;
873 }
874 free(oldurl);
875 free(vpninfo->redirect_url);
876 vpninfo->redirect_url = NULL;
877 }
878 return 0;
879 }
880 }
881
dump_buf(struct openconnect_info * vpninfo,char prefix,char * buf)882 void dump_buf(struct openconnect_info *vpninfo, char prefix, char *buf)
883 {
884 while (*buf) {
885 char *eol = buf;
886 char eol_char = 0;
887
888 while (*eol) {
889 if (*eol == '\r' || *eol == '\n') {
890 eol_char = *eol;
891 *eol = 0;
892 break;
893 }
894 eol++;
895 }
896
897 vpn_progress(vpninfo, PRG_DEBUG, "%c %s\n", prefix, buf);
898 if (!eol_char)
899 break;
900
901 *eol = eol_char;
902 buf = eol + 1;
903 if (eol_char == '\r' && *buf == '\n')
904 buf++;
905 }
906 }
907
dump_buf_hex(struct openconnect_info * vpninfo,int loglevel,char prefix,unsigned char * buf,int len)908 void dump_buf_hex(struct openconnect_info *vpninfo, int loglevel, char prefix, unsigned char *buf, int len)
909 {
910 struct oc_text_buf *line = buf_alloc();
911 int i, j;
912
913 for (i = 0; i < len; i+=16) {
914 buf_truncate(line);
915 buf_append(line, "%04x:", i);
916 for (j = i; j < i+16; j++) {
917 if (!(j & 7))
918 buf_append(line, " ");
919
920 if (j < len)
921 buf_append(line, " %02x", buf[j]);
922 else
923 buf_append(line, " ");
924 }
925 buf_append(line, " |");
926 for (j = i; j < i+16 && j < len; j++)
927 buf_append(line, "%c", isprint(buf[j])? buf[j] : '.');
928 buf_append(line, "|");
929 if (buf_error(line))
930 break;
931 vpn_progress(vpninfo, loglevel, "%c %s\n", prefix, line->data);
932 }
933 buf_free(line);
934 }
935
https_socket_closed(struct openconnect_info * vpninfo)936 static int https_socket_closed(struct openconnect_info *vpninfo)
937 {
938 char c;
939
940 if (!openconnect_https_connected(vpninfo))
941 return 1;
942
943 if (recv(vpninfo->ssl_fd, &c, 1, MSG_PEEK) == 0) {
944 vpn_progress(vpninfo, PRG_DEBUG,
945 _("HTTPS socket closed by peer; reopening\n"));
946 openconnect_close_https(vpninfo, 0);
947 return 1;
948 }
949
950 return 0;
951 }
952
953
954 /* Inputs:
955 * method: GET or POST
956 * vpninfo->hostname: Host DNS name
957 * vpninfo->port: TCP port, typically 443
958 * vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
959 * request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
960 * request_body: POST content
961 * form_buf: Callee-allocated buffer for server content
962 *
963 * Return value:
964 * < 0, on error
965 * >=0, on success, indicating the length of the data in *form_buf
966 */
do_https_request(struct openconnect_info * vpninfo,const char * method,const char * request_body_type,struct oc_text_buf * request_body,char ** form_buf,int fetch_redirect)967 int do_https_request(struct openconnect_info *vpninfo, const char *method,
968 const char *request_body_type, struct oc_text_buf *request_body,
969 char **form_buf, int fetch_redirect)
970 {
971 struct oc_text_buf *buf;
972 int result;
973 int rq_retry;
974 int rlen, pad;
975 int i, auth = 0;
976 int max_redirects = 10;
977
978 if (request_body_type && buf_error(request_body))
979 return buf_error(request_body);
980
981 buf = buf_alloc();
982
983 redirected:
984 if (max_redirects-- <= 0) {
985 result = -EIO;
986 goto out;
987 }
988
989 vpninfo->redirect_type = REDIR_TYPE_NONE;
990
991 if (*form_buf) {
992 free(*form_buf);
993 *form_buf = NULL;
994 }
995
996 /*
997 * A long time ago, I *wanted* to use an HTTP client library like cURL
998 * for this. But we need a *lot* of control over the underlying SSL
999 * transport, and we also have to do horrid tricks like the Juniper NC
1000 * 'GET' request that actaully behaves like a 'CONNECT'.
1001 *
1002 * So the world gained Yet Another HTTP Implementation. Sorry.
1003 *
1004 */
1005 buf_truncate(buf);
1006 buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
1007 if (auth) {
1008 result = gen_authorization_hdr(vpninfo, 0, buf);
1009 if (result)
1010 goto out;
1011
1012 /* Forget existing challenges */
1013 clear_auth_states(vpninfo, vpninfo->http_auth, 0);
1014 }
1015 if (vpninfo->proto->add_http_headers)
1016 vpninfo->proto->add_http_headers(vpninfo, buf);
1017
1018 if (request_body_type) {
1019 rlen = request_body->pos;
1020
1021 /* force body length to be a multiple of 64, to avoid leaking
1022 * password length. */
1023 pad = 64*(1+rlen/64) - rlen;
1024 buf_append(buf, "X-Pad: %0*d\r\n", pad, 0);
1025
1026 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
1027 buf_append(buf, "Content-Length: %d\r\n", (int)rlen);
1028 }
1029 buf_append(buf, "\r\n");
1030
1031 if (request_body_type)
1032 buf_append_bytes(buf, request_body->data, request_body->pos);
1033
1034 if (vpninfo->port == 443)
1035 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
1036 method, vpninfo->hostname,
1037 vpninfo->urlpath ?: "");
1038 else
1039 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
1040 method, vpninfo->hostname, vpninfo->port,
1041 vpninfo->urlpath ?: "");
1042
1043 if (buf_error(buf))
1044 return buf_free(buf);
1045
1046 vpninfo->retry_on_auth_fail = 0;
1047
1048 retry:
1049 if (!https_socket_closed(vpninfo)) {
1050 /* The session is already connected. If we get a failure on
1051 * *sending* the request, try it again immediately with a new
1052 * connection. */
1053 rq_retry = 1;
1054 } else {
1055 rq_retry = 0;
1056 if ((result = openconnect_open_https(vpninfo))) {
1057 vpn_progress(vpninfo, PRG_ERR,
1058 _("Failed to open HTTPS connection to %s\n"),
1059 vpninfo->hostname);
1060 /* We really don't want to return -EINVAL if we have
1061 failed to even connect to the server, because if
1062 we do that openconnect_obtain_cookie() might try
1063 again without XMLPOST... with the same result. */
1064 result = -EIO;
1065 goto out;
1066 }
1067 }
1068
1069 if (vpninfo->dump_http_traffic)
1070 dump_buf(vpninfo, '>', buf->data);
1071
1072 for (i = 0; i < buf->pos; i += 16384) {
1073 result = vpninfo->ssl_write(vpninfo, buf->data + i, MIN(buf->pos - i, 16384) );
1074 if (result < 0) {
1075 if (rq_retry) {
1076 /* Retry if we failed to send the request on
1077 an already-open connection */
1078 openconnect_close_https(vpninfo, 0);
1079 goto retry;
1080 }
1081 /* We'll already have complained about whatever offended us */
1082 goto out;
1083 }
1084 }
1085
1086 result = process_http_response(vpninfo, 0, http_auth_hdrs, buf);
1087 if (result < 0) {
1088 if (rq_retry) {
1089 openconnect_close_https(vpninfo, 0);
1090 vpn_progress(vpninfo, PRG_INFO,
1091 _("Retrying failed %s request on new connection\n"),
1092 method);
1093 /* All the way back to 'redirected' since we need to rebuild
1094 * the request in 'buf' from scratch. */
1095 goto redirected;
1096 }
1097 goto out;
1098 }
1099 if (vpninfo->dump_http_traffic && buf->pos)
1100 dump_buf(vpninfo, '<', buf->data);
1101
1102 if (result == 401 && vpninfo->try_http_auth) {
1103 auth = 1;
1104 goto redirected;
1105 }
1106 if (result != 200 && vpninfo->redirect_url) {
1107 result = handle_redirect(vpninfo);
1108 if (result == 0) {
1109 if (!fetch_redirect)
1110 goto out;
1111 if (fetch_redirect == 2) {
1112 /* Juniper requires we GET after a redirected POST */
1113 method = "GET";
1114 request_body_type = NULL;
1115 }
1116 if (vpninfo->redirect_type == REDIR_TYPE_NEWHOST)
1117 clear_auth_states(vpninfo, vpninfo->http_auth, 1);
1118 goto redirected;
1119 }
1120 goto out;
1121 }
1122 if (!buf->pos || result != 200) {
1123 vpn_progress(vpninfo, PRG_ERR,
1124 _("Unexpected %d result from server\n"),
1125 result);
1126 if (result == 401 || result == 403)
1127 result = -EPERM;
1128 else if (result == 512) /* GlobalProtect invalid username/password */
1129 result = -EACCES;
1130 else
1131 result = -EINVAL;
1132 goto out;
1133 }
1134
1135 *form_buf = buf->data;
1136 buf->data = NULL;
1137 result = buf->pos;
1138
1139 out:
1140 buf_free(buf);
1141 /* On success, clear out all authentication state for the next request */
1142 clear_auth_states(vpninfo, vpninfo->http_auth, 1);
1143 return result;
1144 }
1145
openconnect_create_useragent(const char * base)1146 char *openconnect_create_useragent(const char *base)
1147 {
1148 char *uagent;
1149
1150 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1151 return NULL;
1152
1153 return uagent;
1154 }
1155
proxy_gets(struct openconnect_info * vpninfo,char * buf,size_t len)1156 static int proxy_gets(struct openconnect_info *vpninfo, char *buf, size_t len)
1157 {
1158 return cancellable_gets(vpninfo, vpninfo->proxy_fd, buf, len);
1159 }
1160
proxy_write(struct openconnect_info * vpninfo,char * buf,size_t len)1161 static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len)
1162 {
1163 return cancellable_send(vpninfo, vpninfo->proxy_fd, buf, len);
1164 }
1165
proxy_read(struct openconnect_info * vpninfo,char * buf,size_t len)1166 static int proxy_read(struct openconnect_info *vpninfo, char *buf, size_t len)
1167 {
1168 return cancellable_recv(vpninfo, vpninfo->proxy_fd, buf, len);
1169 }
1170
1171 static const char *socks_errors[] = {
1172 N_("request granted"),
1173 N_("general failure"),
1174 N_("connection not allowed by ruleset"),
1175 N_("network unreachable"),
1176 N_("host unreachable"),
1177 N_("connection refused by destination host"),
1178 N_("TTL expired"),
1179 N_("command not supported / protocol error"),
1180 N_("address type not supported")
1181 };
1182
socks_password_auth(struct openconnect_info * vpninfo)1183 static int socks_password_auth(struct openconnect_info *vpninfo)
1184 {
1185 int ul, pl, i;
1186 char buf[1024];
1187
1188 if (!vpninfo->proxy_user || !vpninfo->proxy_pass) {
1189 vpn_progress(vpninfo, PRG_ERR,
1190 _("SOCKS server requested username/password but we have none\n"));
1191 return -EIO;
1192 }
1193 ul = strlen(vpninfo->proxy_user);
1194 pl = strlen(vpninfo->proxy_pass);
1195
1196 if (ul > 255 || pl > 255) {
1197 vpn_progress(vpninfo, PRG_ERR,
1198 _("Username and password for SOCKS authentication must be < 255 bytes\n"));
1199 return -EINVAL;
1200 }
1201
1202 buf[0] = 1;
1203 buf[1] = ul;
1204 memcpy(buf + 2, vpninfo->proxy_user, ul);
1205 buf[2 + ul] = pl;
1206 memcpy(buf + 3 + ul, vpninfo->proxy_pass, pl);
1207
1208 i = proxy_write(vpninfo, buf, 3 + ul + pl);
1209 /* Don't leave passwords lying around if we can easily avoid it... */
1210 memset(buf, 0, sizeof(buf));
1211 if (i < 0) {
1212 vpn_progress(vpninfo, PRG_ERR,
1213 _("Error writing auth request to SOCKS proxy: %s\n"),
1214 strerror(-i));
1215 return i;
1216 }
1217
1218
1219 if ((i = proxy_read(vpninfo, buf, 2)) < 0) {
1220 vpn_progress(vpninfo, PRG_ERR,
1221 _("Error reading auth response from SOCKS proxy: %s\n"),
1222 strerror(-i));
1223 return i;
1224 }
1225
1226 if (buf[0] != 1) {
1227 vpn_progress(vpninfo, PRG_ERR,
1228 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1229 buf[0], buf[1]);
1230 return -EIO;
1231 }
1232 if (buf[1] == 0) {
1233 vpn_progress(vpninfo, PRG_DEBUG,
1234 _("Authenticated to SOCKS server using password\n"));
1235 return 0;
1236 } else {
1237 vpn_progress(vpninfo, PRG_ERR,
1238 _("Password authentication to SOCKS server failed\n"));
1239 return -EIO;
1240 }
1241 }
1242
1243 #define SOCKS_AUTH_NONE 0 /* RFC1928 */
1244 #define SOCKS_AUTH_GSSAPI 1 /* RFC1961 */
1245 #define SOCKS_AUTH_PASSWORD 2 /* RFC1929 */
1246 #define SOCKS_AUTH_NO_ACCEPTABLE 0xff /* RFC1928 */
1247
process_socks_proxy(struct openconnect_info * vpninfo)1248 static int process_socks_proxy(struct openconnect_info *vpninfo)
1249 {
1250 char buf[1024];
1251 int i, nr_auth_methods = 0;
1252
1253 buf[0] = 5; /* SOCKS version */
1254
1255 buf[2 + nr_auth_methods++] = SOCKS_AUTH_NONE;
1256 #if defined(HAVE_GSSAPI) || defined(_WIN32)
1257 if (vpninfo->proxy_auth[AUTH_TYPE_GSSAPI].state > AUTH_FAILED &&
1258 !vpninfo->proxy_user && !vpninfo->proxy_pass)
1259 buf[2 + nr_auth_methods++] = SOCKS_AUTH_GSSAPI;
1260 #endif
1261 /*
1262 * Basic auth is disabled by default. But for SOCKS, if the user has
1263 * actually provided a password then that should implicitly allow
1264 * basic auth since that's all that SOCKS can do. We shouldn't force
1265 * the user to also add --proxy-auth=basic on the command line.
1266 */
1267 if ((vpninfo->proxy_auth[AUTH_TYPE_BASIC].state > AUTH_FAILED ||
1268 vpninfo->proxy_auth[AUTH_TYPE_BASIC].state == AUTH_DEFAULT_DISABLED) &&
1269 vpninfo->proxy_user && vpninfo->proxy_pass)
1270 buf[2 + nr_auth_methods++] = SOCKS_AUTH_PASSWORD;
1271
1272 buf[1] = nr_auth_methods;
1273
1274 if ((i = proxy_write(vpninfo, buf, 2 + nr_auth_methods)) < 0) {
1275 vpn_progress(vpninfo, PRG_ERR,
1276 _("Error writing auth request to SOCKS proxy: %s\n"),
1277 strerror(-i));
1278 return i;
1279 }
1280
1281 if ((i = proxy_read(vpninfo, buf, 2)) < 0) {
1282 vpn_progress(vpninfo, PRG_ERR,
1283 _("Error reading auth response from SOCKS proxy: %s\n"),
1284 strerror(-i));
1285 return i;
1286 }
1287 if (buf[0] != 5) {
1288 vpn_progress(vpninfo, PRG_ERR,
1289 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1290 buf[0], buf[1]);
1291 return -EIO;
1292 }
1293 switch ((unsigned char)buf[1]) {
1294 case SOCKS_AUTH_NONE:
1295 /* No authentication */
1296 break;
1297
1298 case SOCKS_AUTH_GSSAPI:
1299 #if defined(HAVE_GSSAPI) || defined(_WIN32)
1300 vpn_progress(vpninfo, PRG_DEBUG,
1301 _("SOCKS server requested GSSAPI authentication\n"));
1302 if (socks_gssapi_auth(vpninfo))
1303 return -EIO;
1304 break;
1305 #else
1306 /* This should never happen since we didn't ask for it! */
1307 vpn_progress(vpninfo, PRG_ERR,
1308 _("SOCKS server requested GSSAPI authentication\n"));
1309 return -EIO;
1310 #endif
1311
1312 case SOCKS_AUTH_PASSWORD:
1313 vpn_progress(vpninfo, PRG_DEBUG,
1314 _("SOCKS server requested password authentication\n"));
1315 if (socks_password_auth(vpninfo))
1316 return -EIO;
1317 break;
1318
1319 case SOCKS_AUTH_NO_ACCEPTABLE:
1320 vpn_progress(vpninfo, PRG_ERR,
1321 _("SOCKS server requires authentication\n"));
1322 #if !defined(HAVE_GSSAPI) && !defined(_WIN32)
1323 vpn_progress(vpninfo, PRG_INFO,
1324 _("This version of OpenConnect was built without GSSAPI support\n"));
1325 #endif
1326 return -EIO;
1327
1328 default:
1329 vpn_progress(vpninfo, PRG_ERR,
1330 _("SOCKS server requested unknown authentication type %02x\n"),
1331 (unsigned char)buf[1]);
1332 return -EIO;
1333 }
1334
1335 vpn_progress(vpninfo, PRG_INFO,
1336 _("Requesting SOCKS proxy connection to %s:%d\n"),
1337 vpninfo->hostname, vpninfo->port);
1338
1339 buf[0] = 5; /* SOCKS version */
1340 buf[1] = 1; /* CONNECT */
1341 buf[2] = 0; /* Reserved */
1342 buf[3] = 3; /* Address type is domain name */
1343 buf[4] = strlen(vpninfo->hostname);
1344 strcpy((char *)buf + 5, vpninfo->hostname);
1345 i = strlen(vpninfo->hostname) + 5;
1346 store_be16(buf + i, vpninfo->port);
1347 i += 2;
1348
1349 if ((i = proxy_write(vpninfo, buf, i)) < 0) {
1350 vpn_progress(vpninfo, PRG_ERR,
1351 _("Error writing connect request to SOCKS proxy: %s\n"),
1352 strerror(-i));
1353 return i;
1354 }
1355 /* Read 5 bytes -- up to and including the first byte of the returned
1356 address (which might be the length byte of a domain name) */
1357 if ((i = proxy_read(vpninfo, buf, 5)) < 0) {
1358 vpn_progress(vpninfo, PRG_ERR,
1359 _("Error reading connect response from SOCKS proxy: %s\n"),
1360 strerror(-i));
1361 return i;
1362 }
1363 if (i != 5 || buf[0] != 5) {
1364 vpn_progress(vpninfo, PRG_ERR,
1365 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1366 buf[0], buf[1]);
1367 return -EIO;
1368 }
1369 if (buf[1]) {
1370 unsigned char err = buf[1];
1371 if (err < sizeof(socks_errors) / sizeof(socks_errors[0]))
1372 vpn_progress(vpninfo, PRG_ERR,
1373 _("SOCKS proxy error %02x: %s\n"),
1374 err, _(socks_errors[err]));
1375 else
1376 vpn_progress(vpninfo, PRG_ERR,
1377 _("SOCKS proxy error %02x\n"), err);
1378 return -EIO;
1379 }
1380
1381 /* Connect responses contain an address */
1382 switch (buf[3]) {
1383 case 1: /* Legacy IP */
1384 i = 5;
1385 break;
1386 case 3: /* Domain name */
1387 i = buf[4] + 2;
1388 break;
1389 case 4: /* IPv6 */
1390 i = 17;
1391 break;
1392 default:
1393 vpn_progress(vpninfo, PRG_ERR,
1394 _("Unexpected address type %02x in SOCKS connect response\n"),
1395 buf[3]);
1396 return -EIO;
1397 }
1398
1399 if ((i = proxy_read(vpninfo, buf, i)) < 0) {
1400 vpn_progress(vpninfo, PRG_ERR,
1401 _("Error reading connect response from SOCKS proxy: %s\n"),
1402 strerror(-i));
1403 return i;
1404 }
1405 return 0;
1406 }
1407
process_http_proxy(struct openconnect_info * vpninfo)1408 static int process_http_proxy(struct openconnect_info *vpninfo)
1409 {
1410 struct oc_text_buf *reqbuf;
1411 int result;
1412 int auth = vpninfo->proxy_close_during_auth;
1413
1414 vpninfo->proxy_close_during_auth = 0;
1415
1416 vpn_progress(vpninfo, PRG_INFO,
1417 _("Requesting HTTP proxy connection to %s:%d\n"),
1418 vpninfo->hostname, vpninfo->port);
1419
1420 retry:
1421 reqbuf = buf_alloc();
1422 buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1423 if (vpninfo->port == 443)
1424 buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1425 else
1426 buf_append(reqbuf, "Host: %s:%d\r\n", vpninfo->hostname, vpninfo->port);
1427 buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1428 buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1429 buf_append(reqbuf, "Connection: keep-alive\r\n");
1430 buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1431 if (auth) {
1432 result = gen_authorization_hdr(vpninfo, 1, reqbuf);
1433 if (result) {
1434 buf_free(reqbuf);
1435 return result;
1436 }
1437 /* Forget existing challenges */
1438 clear_auth_states(vpninfo, vpninfo->proxy_auth, 0);
1439 }
1440 buf_append(reqbuf, "\r\n");
1441
1442 if (buf_error(reqbuf))
1443 return buf_free(reqbuf);
1444
1445 if (vpninfo->dump_http_traffic)
1446 dump_buf(vpninfo, '>', reqbuf->data);
1447
1448 result = proxy_write(vpninfo, reqbuf->data, reqbuf->pos);
1449 if (result < 0) {
1450 buf_free(reqbuf);
1451 vpn_progress(vpninfo, PRG_ERR,
1452 _("Sending proxy request failed: %s\n"),
1453 strerror(-result));
1454 return result;
1455 }
1456
1457 result = process_http_response(vpninfo, 1, proxy_auth_hdrs, reqbuf);
1458 buf_free(reqbuf);
1459 if (result < 0)
1460 return -EINVAL;
1461
1462 if (result == 407) {
1463 /* If the proxy asked us to close the connection, do so */
1464 if (vpninfo->proxy_close_during_auth)
1465 return -EAGAIN;
1466
1467 auth = 1;
1468 goto retry;
1469 }
1470
1471 if (result == 200)
1472 return 0;
1473
1474 vpn_progress(vpninfo, PRG_ERR,
1475 _("Proxy CONNECT request failed: %d\n"), result);
1476 return -EIO;
1477 }
1478
process_proxy(struct openconnect_info * vpninfo,int ssl_sock)1479 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1480 {
1481 int ret;
1482
1483 vpninfo->proxy_fd = ssl_sock;
1484 vpninfo->ssl_read = proxy_read;
1485 vpninfo->ssl_write = proxy_write;
1486 vpninfo->ssl_gets = proxy_gets;
1487
1488 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1489 ret = process_http_proxy(vpninfo);
1490 else if (!strcmp(vpninfo->proxy_type, "socks") ||
1491 !strcmp(vpninfo->proxy_type, "socks5"))
1492 ret = process_socks_proxy(vpninfo);
1493 else {
1494 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1495 vpninfo->proxy_type);
1496 ret = -EIO;
1497 }
1498
1499 vpninfo->proxy_fd = -1;
1500 if (!vpninfo->proxy_close_during_auth)
1501 clear_auth_states(vpninfo, vpninfo->proxy_auth, 1);
1502
1503 return ret;
1504 }
1505
openconnect_set_http_proxy(struct openconnect_info * vpninfo,const char * proxy)1506 int openconnect_set_http_proxy(struct openconnect_info *vpninfo,
1507 const char *proxy)
1508 {
1509 char *p;
1510 int ret;
1511
1512 free(vpninfo->proxy_type);
1513 vpninfo->proxy_type = NULL;
1514 free(vpninfo->proxy);
1515 vpninfo->proxy = NULL;
1516
1517 ret = internal_parse_url(proxy, &vpninfo->proxy_type, &vpninfo->proxy,
1518 &vpninfo->proxy_port, NULL, 80);
1519 if (ret) {
1520 vpn_progress(vpninfo, PRG_ERR, _("Failed to parse proxy '%s'\n"), proxy);
1521 return ret;
1522 }
1523
1524 p = strrchr(vpninfo->proxy, '@');
1525 if (p) {
1526 /* Proxy username/password */
1527 *p = 0;
1528 vpninfo->proxy_user = vpninfo->proxy;
1529 vpninfo->proxy = strdup(p + 1);
1530 p = strchr(vpninfo->proxy_user, ':');
1531 if (p) {
1532 *p = 0;
1533 vpninfo->proxy_pass = strdup(p + 1);
1534 xmlURIUnescapeString(vpninfo->proxy_pass, 0, vpninfo->proxy_pass);
1535 }
1536 xmlURIUnescapeString(vpninfo->proxy_user, 0, vpninfo->proxy_user);
1537 }
1538
1539 if (vpninfo->proxy_type &&
1540 strcmp(vpninfo->proxy_type, "http") &&
1541 strcmp(vpninfo->proxy_type, "socks") &&
1542 strcmp(vpninfo->proxy_type, "socks5")) {
1543 vpn_progress(vpninfo, PRG_ERR,
1544 _("Only http or socks(5) proxies supported\n"));
1545 free(vpninfo->proxy_type);
1546 vpninfo->proxy_type = NULL;
1547 free(vpninfo->proxy);
1548 vpninfo->proxy = NULL;
1549 return -EINVAL;
1550 }
1551
1552 return 0;
1553 }
1554
http_common_headers(struct openconnect_info * vpninfo,struct oc_text_buf * buf)1555 void http_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
1556 {
1557 struct oc_vpn_option *opt;
1558
1559 if (vpninfo->port == 443)
1560 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
1561 else
1562 buf_append(buf, "Host: %s:%d\r\n", vpninfo->hostname, vpninfo->port);
1563 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
1564
1565 if (vpninfo->cookies) {
1566 buf_append(buf, "Cookie: ");
1567 for (opt = vpninfo->cookies; opt; opt = opt->next)
1568 buf_append(buf, "%s=%s%s", opt->option,
1569 opt->value, opt->next ? "; " : "\r\n");
1570 }
1571 }
1572