f5.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. /*
  2. * OpenConnect (SSL + DTLS) VPN client
  3. *
  4. * Copyright © 2020-2021 David Woodhouse, Daniel Lenski
  5. *
  6. * Author: David Woodhouse <dwmw2@infradead.org>, Daniel Lenski <dlenski@gmail.com>
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public License
  10. * version 2.1, as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. */
  17. #include <config.h>
  18. #include "openconnect-internal.h"
  19. #include "ppp.h"
  20. #include <libxml/HTMLparser.h>
  21. #include <libxml/HTMLtree.h>
  22. #include <unistd.h>
  23. #include <fcntl.h>
  24. #include <sys/types.h>
  25. #include <time.h>
  26. #include <string.h>
  27. #include <ctype.h>
  28. #include <errno.h>
  29. #include <stdlib.h>
  30. #include <stdio.h>
  31. #include <stdarg.h>
  32. #define XCAST(x) ((const xmlChar *)(x))
  33. /* XX: some F5 VPNs simply do not have a static HTML form to parse, but
  34. * only a mess of JavaScript which creates a dynamic form that's
  35. * functionally equivalent to the following:
  36. *
  37. * <form id="auth_form" method="post">
  38. * <input type="text" name="username"/>
  39. * <input type="password" name="password"/>
  40. * </form>
  41. */
  42. static struct oc_auth_form *plain_auth_form(void)
  43. {
  44. struct oc_auth_form *form;
  45. struct oc_form_opt *opt, *opt2;
  46. form = calloc(1, sizeof(*form));
  47. if (!form) {
  48. nomem:
  49. free_auth_form(form);
  50. return NULL;
  51. }
  52. if ((form->auth_id = strdup("auth_form")) == NULL)
  53. goto nomem;
  54. opt = form->opts = calloc(1, sizeof(*opt));
  55. if (!opt)
  56. goto nomem;
  57. opt->label = strdup("username:");
  58. opt->name = strdup("username");
  59. opt->type = OC_FORM_OPT_TEXT;
  60. opt2 = opt->next = calloc(1, sizeof(*opt2));
  61. if (!opt2)
  62. goto nomem;
  63. opt2->label = strdup("password:");
  64. opt2->name = strdup("password");
  65. opt2->type = OC_FORM_OPT_PASSWORD;
  66. return form;
  67. }
  68. static int check_cookie_success(struct openconnect_info *vpninfo)
  69. {
  70. struct oc_vpn_option *cookie;
  71. const char *session = NULL, *f5_st = NULL;
  72. /* XX: The MRHSession cookie is often set repeatedly to new values prior
  73. * to the completion of authentication, so it is not a sufficient
  74. * indicator of successfully completed authentication by itself.
  75. *
  76. * The combination of the MRHSession and F5_ST cookies appears to be
  77. * reliable indicator. F5_ST is a "session timeout" cookie (see
  78. * https://support.f5.com/csp/article/K15387).
  79. */
  80. for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
  81. if (!strcmp(cookie->option, "MRHSession"))
  82. session = cookie->value;
  83. else if (!strcmp(cookie->option, "F5_ST"))
  84. f5_st = cookie->value;
  85. }
  86. if (session && f5_st) {
  87. free(vpninfo->cookie);
  88. if (asprintf(&vpninfo->cookie, "MRHSession=%s; F5_ST=%s", session, f5_st) <= 0)
  89. return -ENOMEM;
  90. return 0;
  91. }
  92. return -ENOENT;
  93. }
  94. int f5_obtain_cookie(struct openconnect_info *vpninfo)
  95. {
  96. int ret, form_order=0;
  97. xmlDocPtr doc = NULL;
  98. xmlNode *node;
  99. struct oc_text_buf *req_buf = NULL;
  100. struct oc_auth_form *form = NULL;
  101. char *form_id = NULL;
  102. req_buf = buf_alloc();
  103. if ((ret = buf_error(req_buf)))
  104. goto out;
  105. while (++form_order) {
  106. char *resp_buf = NULL;
  107. char *url;
  108. if (req_buf && req_buf->pos)
  109. ret = do_https_request(vpninfo, "POST",
  110. "application/x-www-form-urlencoded",
  111. req_buf, &resp_buf, NULL, HTTP_REDIRECT_TO_GET);
  112. else
  113. ret = do_https_request(vpninfo, "GET", NULL, NULL, &resp_buf, NULL, HTTP_REDIRECT_TO_GET);
  114. if (!check_cookie_success(vpninfo)) {
  115. free(resp_buf);
  116. ret = 0;
  117. break;
  118. }
  119. if (ret < 0)
  120. break;
  121. url = internal_get_url(vpninfo);
  122. if (!url) {
  123. free(resp_buf);
  124. nomem:
  125. ret = -ENOMEM;
  126. break;
  127. }
  128. doc = htmlReadMemory(resp_buf, ret, url, NULL,
  129. HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING|HTML_PARSE_NONET);
  130. free(url);
  131. free(resp_buf);
  132. if (!doc) {
  133. vpn_progress(vpninfo, PRG_ERR,
  134. _("Failed to parse HTML document\n"));
  135. ret = -EINVAL;
  136. break;
  137. }
  138. buf_truncate(req_buf);
  139. node = find_form_node(doc);
  140. if (!node && form_order==1) {
  141. /* XX: some F5 VPNs simply do not have a static HTML form to parse */
  142. vpn_progress(vpninfo, PRG_ERR,
  143. _("WARNING: no HTML login form found; assuming username and password fields\n"));
  144. if ((form = plain_auth_form()) == NULL)
  145. goto nomem;
  146. } else {
  147. form = parse_form_node(vpninfo, node, NULL, NULL);
  148. if (form_order==1 && (xmlnode_get_prop(node, "id", &form_id) || strcmp(form_id, "auth_form"))) {
  149. vpn_progress(vpninfo, PRG_ERR, _("Unknown form ID '%s' (expected 'auth_form')\n"),
  150. form_id);
  151. fprintf(stderr, _("Dumping unknown HTML form:\n"));
  152. htmlNodeDumpFileFormat(stderr, node->doc, node, NULL, 1);
  153. ret = -EINVAL;
  154. break;
  155. }
  156. }
  157. if (!form) {
  158. ret = -EINVAL;
  159. break;
  160. }
  161. /* XX: do_gen_tokencode would go here, if we knew of any
  162. * token-based 2FA options for F5.
  163. */
  164. do {
  165. ret = process_auth_form(vpninfo, form);
  166. } while (ret == OC_FORM_RESULT_NEWGROUP);
  167. if (ret)
  168. goto out;
  169. append_form_opts(vpninfo, form, req_buf);
  170. if ((ret = buf_error(req_buf)))
  171. goto out;
  172. if (form->action) {
  173. vpninfo->redirect_url = form->action;
  174. form->action = NULL;
  175. }
  176. free_auth_form(form);
  177. form = NULL;
  178. if (vpninfo->redirect_url)
  179. handle_redirect(vpninfo);
  180. xmlFreeDoc(doc);
  181. doc = NULL;
  182. }
  183. out:
  184. if (doc)
  185. xmlFreeDoc(doc);
  186. free(form_id);
  187. if (form) free_auth_form(form);
  188. if (req_buf) buf_free(req_buf);
  189. return ret;
  190. }
  191. /*
  192. * Parse the 'favorites' profile information from
  193. * /vdesk/vpn/index.php3?outform=xml&client_version=2.0
  194. * which looks something like this:
  195. *
  196. * <?xml version="1.0" encoding="utf-8"?>
  197. * <favorites type="VPN" limited="YES">
  198. * <favorite id="/Common/demo_vpn_resource">
  199. * <caption>demo_vpn_resource</caption>
  200. * <name>/Common/demo_vpn_resource</name>
  201. * <params>resourcename=/Common/demo_vpn_resource</params>
  202. * </favorite>
  203. * </favorites>
  204. *
  205. * Extract the content of the "params" node which is needed for the
  206. * next request.
  207. */
  208. static int parse_profile(struct openconnect_info *vpninfo, char *buf, int len,
  209. char **params)
  210. {
  211. xmlDocPtr xml_doc;
  212. xmlNode *xml_node, *xml_node2, *xml_node3;
  213. char *type = NULL;
  214. int ret;
  215. if (!buf || !len)
  216. return -EINVAL;
  217. xml_doc = xmlReadMemory(buf, len, NULL, NULL,
  218. XML_PARSE_NOERROR|XML_PARSE_RECOVER);
  219. if (!xml_doc) {
  220. vpn_progress(vpninfo, PRG_ERR,
  221. _("Failed to parse F5 profile response\n"));
  222. vpn_progress(vpninfo, PRG_DEBUG,
  223. _("Response was:%s\n"), buf);
  224. return -EINVAL;
  225. }
  226. xml_node = xmlDocGetRootElement(xml_doc);
  227. for (; xml_node; xml_node = xml_node->next) {
  228. if (xml_node->type != XML_ELEMENT_NODE)
  229. continue;
  230. if (!xmlnode_is_named(xml_node, "favorites"))
  231. continue;
  232. type = (char *)xmlGetProp(xml_node, XCAST("type"));
  233. if (!type)
  234. continue;
  235. if (strcmp(type, "VPN")) {
  236. free(type);
  237. continue;
  238. }
  239. free(type);
  240. for (xml_node2 = xmlFirstElementChild(xml_node);
  241. xml_node2;
  242. xml_node2 = xmlNextElementSibling(xml_node2)) {
  243. if (!xmlnode_is_named(xml_node2, "favorite"))
  244. continue;
  245. for (xml_node3 = xmlFirstElementChild(xml_node2);
  246. xml_node3;
  247. xml_node3 = xmlNextElementSibling(xml_node3)) {
  248. if (!xmlnode_is_named(xml_node3, "params"))
  249. continue;
  250. *params = (char *)xmlNodeGetContent(xml_node3);
  251. ret = 0;
  252. goto out;
  253. }
  254. }
  255. }
  256. vpn_progress(vpninfo, PRG_ERR,
  257. _("Failed to find VPN profile parameters\n"));
  258. vpn_progress(vpninfo, PRG_DEBUG,
  259. _("Response was:%s\n"), buf);
  260. ret = -EINVAL;
  261. out:
  262. xmlFreeDoc(xml_doc);
  263. return ret;
  264. }
  265. static int xmlnode_bool_or_int_value(struct openconnect_info *vpninfo, xmlNode *node)
  266. {
  267. int ret = -1;
  268. char *content = (char *)xmlNodeGetContent(node);
  269. if (!content)
  270. return -1;
  271. if (isdigit(content[0]))
  272. ret = atoi(content);
  273. if (!strcasecmp(content, "yes") || !strcasecmp(content, "on"))
  274. ret = 1;
  275. if (!strcasecmp(content, "no") || !strcasecmp(content, "off"))
  276. ret = 0;
  277. free(content);
  278. return ret;
  279. }
  280. static int parse_options(struct openconnect_info *vpninfo, char *buf, int len,
  281. char **session_id, char **ur_z, int *ipv4, int *ipv6, int *hdlc)
  282. {
  283. xmlNode *fav_node, *obj_node, *xml_node;
  284. xmlDocPtr xml_doc;
  285. int ret = 0, n_dns = 0, n_nbns = 0, default_route = 0, dtls = 0, dtls_port = 0;
  286. char *s = NULL;
  287. struct oc_text_buf *domains = NULL;
  288. if (!buf || !len)
  289. return -EINVAL;
  290. xml_doc = xmlReadMemory(buf, len, NULL, NULL,
  291. XML_PARSE_NOERROR|XML_PARSE_RECOVER);
  292. if (!xml_doc) {
  293. vpn_progress(vpninfo, PRG_ERR,
  294. _("Failed to parse F5 options response\n"));
  295. vpn_progress(vpninfo, PRG_DEBUG,
  296. _("Response was:%s\n"), buf);
  297. return -EINVAL;
  298. }
  299. fav_node = xmlDocGetRootElement(xml_doc);
  300. if (!xmlnode_is_named(fav_node, "favorite"))
  301. goto err;
  302. obj_node = xmlFirstElementChild(fav_node);
  303. if (!xmlnode_is_named(obj_node, "object"))
  304. goto err;
  305. struct oc_vpn_option *new_opts = NULL;
  306. struct oc_ip_info new_ip_info = {};
  307. domains = buf_alloc();
  308. for (xml_node = xmlFirstElementChild(obj_node);
  309. xml_node;
  310. xml_node = xmlNextElementSibling(xml_node)) {
  311. if (xmlnode_is_named(xml_node, "ur_Z"))
  312. *ur_z = (char *)xmlNodeGetContent(xml_node);
  313. else if (xmlnode_is_named(xml_node, "Session_ID"))
  314. *session_id = (char *)xmlNodeGetContent(xml_node);
  315. else if (xmlnode_is_named(xml_node, "IPV4_0"))
  316. *ipv4 = xmlnode_bool_or_int_value(vpninfo, xml_node);
  317. else if (xmlnode_is_named(xml_node, "IPV6_0")) {
  318. if (!vpninfo->disable_ipv6)
  319. *ipv6 = xmlnode_bool_or_int_value(vpninfo, xml_node);
  320. } else if (xmlnode_is_named(xml_node, "hdlc_framing"))
  321. *hdlc = xmlnode_bool_or_int_value(vpninfo, xml_node);
  322. else if (xmlnode_is_named(xml_node, "idle_session_timeout")) {
  323. int sec = vpninfo->idle_timeout = xmlnode_bool_or_int_value(vpninfo, xml_node);
  324. vpn_progress(vpninfo, PRG_INFO, _("Idle timeout is %d minutes\n"), sec/60);
  325. } else if (xmlnode_is_named(xml_node, "tunnel_dtls"))
  326. dtls = xmlnode_bool_or_int_value(vpninfo, xml_node);
  327. else if (xmlnode_is_named(xml_node, "tunnel_port_dtls"))
  328. dtls_port = xmlnode_bool_or_int_value(vpninfo, xml_node);
  329. else if (xmlnode_is_named(xml_node, "dtls_v1_2_supported"))
  330. vpninfo->dtls12 = xmlnode_bool_or_int_value(vpninfo, xml_node);
  331. else if (xmlnode_is_named(xml_node, "UseDefaultGateway0")) {
  332. default_route = xmlnode_bool_or_int_value(vpninfo, xml_node);
  333. if (default_route)
  334. vpn_progress(vpninfo, PRG_INFO, _("Got default routes\n"));
  335. } else if (xmlnode_is_named(xml_node, "SplitTunneling0")) {
  336. int st = xmlnode_bool_or_int_value(vpninfo, xml_node);
  337. vpn_progress(vpninfo, PRG_INFO, _("Got SplitTunneling0 value of %d\n"), st);
  338. /* XX: Should we ignore split-{in,ex}cludes if this is zero? */
  339. }
  340. /* XX: This is an objectively stupid way to use XML, a hierarchical data format. */
  341. else if ( (!strncmp((char *)xml_node->name, "DNS", 3) && isdigit(xml_node->name[3]))
  342. || (!strncmp((char *)xml_node->name, "DNS6_", 5) && isdigit(xml_node->name[5])) ) {
  343. free(s);
  344. s = (char *)xmlNodeGetContent(xml_node);
  345. if (s && *s) {
  346. vpn_progress(vpninfo, PRG_INFO, _("Got DNS server %s\n"), s);
  347. if (n_dns < 3) new_ip_info.dns[n_dns++] = add_option_steal(&new_opts, "DNS", &s);
  348. }
  349. } else if (!strncmp((char *)xml_node->name, "WINS", 4) && isdigit(xml_node->name[4])) {
  350. free(s);
  351. s = (char *)xmlNodeGetContent(xml_node);
  352. if (s && *s) {
  353. vpn_progress(vpninfo, PRG_INFO, _("Got WINS/NBNS server %s\n"), s);
  354. if (n_nbns < 3) new_ip_info.dns[n_nbns++] = add_option_steal(&new_opts, "WINS", &s);
  355. }
  356. } else if (!strncmp((char *)xml_node->name, "DNSSuffix", 9) && isdigit(xml_node->name[9])) {
  357. free(s);
  358. s = (char *)xmlNodeGetContent(xml_node);
  359. if (s && *s) {
  360. vpn_progress(vpninfo, PRG_INFO, _("Got search domain %s\n"), s);
  361. buf_append(domains, "%s ", s);
  362. }
  363. }
  364. /* XX: Like the above, but even stupider because one tag can contain multiple space-separated values. */
  365. else if ( (!strncmp((char *)xml_node->name, "LAN", 3) && isdigit((char)xml_node->name[3]))
  366. || (!strncmp((char *)xml_node->name, "LAN6_", 5) && isdigit((char)xml_node->name[5]))
  367. || (!strncmp((char *)xml_node->name, "ExcludeSubnets", 14) && isdigit((char)xml_node->name[14]))
  368. || (!strncmp((char *)xml_node->name, "ExcludeSubnets6_", 16) && isdigit((char)xml_node->name[16]))) {
  369. free(s);
  370. s = (char *)xmlNodeGetContent(xml_node);
  371. if (s && *s) {
  372. char *word, *next;
  373. int is_exclude = (xml_node->name[0] == 'E');
  374. const char *option = is_exclude ? "split-exclude" : "split-include";
  375. for (word = s; *word; word = next) {
  376. for (next = word; *next && !isspace(*next); next++);
  377. if (*next)
  378. *next++ = 0;
  379. if (next == word + 1)
  380. continue;
  381. struct oc_split_include *inc = malloc(sizeof(*inc));
  382. if (!inc)
  383. continue;
  384. inc->route = add_option_dup(&new_opts, option, word, -1);
  385. if (is_exclude) {
  386. inc->next = new_ip_info.split_excludes;
  387. new_ip_info.split_excludes = inc;
  388. vpn_progress(vpninfo, PRG_INFO, _("Got split exclude route %s\n"), word);
  389. } else {
  390. inc->next = new_ip_info.split_includes;
  391. new_ip_info.split_includes = inc;
  392. vpn_progress(vpninfo, PRG_INFO, _("Got split include route %s\n"), word);
  393. }
  394. }
  395. }
  396. }
  397. }
  398. if (dtls && dtls_port && vpninfo->dtls_state == DTLS_NOSECRET) {
  399. vpn_progress(vpninfo, PRG_INFO, _("DTLS is enabled on port %d\n"), dtls_port);
  400. if (!*hdlc) {
  401. udp_sockaddr(vpninfo, dtls_port);
  402. vpninfo->dtls_state = DTLS_SECRET;
  403. } else {
  404. /* XX: HDLC-like framing (RFC1662) means that tunneled packets may double in size as part of
  405. * their on-the-wire encapsulation, while efficient datagram transport requires calculation
  406. * of a predictable maximum transfer unit.
  407. *
  408. * We hope no servers expect us to combine them. If they do, we should reject DTLS.
  409. */
  410. vpn_progress(vpninfo, PRG_ERR,
  411. _("WARNING: Server enables DTLS, but also requires HDLC. Disabling DTLS,\n"
  412. " because HDLC prevents determination of efficient and consistent MTU.\n"));
  413. }
  414. }
  415. if (default_route && *ipv4)
  416. new_ip_info.netmask = add_option_dup(&new_opts, "netmask", "0.0.0.0", -1);
  417. if (default_route && *ipv6)
  418. new_ip_info.netmask6 = add_option_dup(&new_opts, "netmask6", "::/0", -1);
  419. if (buf_error(domains) == 0 && domains->pos > 0) {
  420. domains->data[domains->pos-1] = '\0';
  421. new_ip_info.domain = add_option_steal(&new_opts, "search", &domains->data);
  422. }
  423. buf_free(domains);
  424. ret = install_vpn_opts(vpninfo, new_opts, &new_ip_info);
  425. if (ret || (*ipv4 < 1 && *ipv6 < 1) || !*ur_z || !*session_id) {
  426. free_optlist(new_opts);
  427. free_split_routes(&new_ip_info);
  428. err:
  429. vpn_progress(vpninfo, PRG_ERR,
  430. _("Failed to find VPN options\n"));
  431. vpn_progress(vpninfo, PRG_DEBUG,
  432. _("Response was:%s\n"), buf);
  433. ret = -EINVAL;
  434. }
  435. xmlFreeDoc(xml_doc);
  436. free(s);
  437. return ret;
  438. }
  439. static int get_ip_address(struct openconnect_info *vpninfo, char *header, char *val)
  440. {
  441. struct oc_ppp *ppp = vpninfo->ppp;
  442. if (!ppp || ppp->ppp_state != PPPS_DEAD)
  443. return 0;
  444. /* If the addresses were already negotiated once in PPP and this
  445. * is a reconnect, they'll be in vpninfo->ip_info.addr*. In that
  446. * case don't overwrite them, and let it correctly abort if the
  447. * server rejects the same addresses this time round. */
  448. if (!strcasecmp(header, "X-VPN-client-IP")) {
  449. vpn_progress(vpninfo, PRG_INFO,
  450. _("Got Legacy IP address %s\n"), val);
  451. if (!vpninfo->ip_info.addr)
  452. ppp->out_ipv4_addr.s_addr = inet_addr(val);
  453. } else if (!strcasecmp(header, "X-VPN-client-IPv6")) {
  454. vpn_progress(vpninfo, PRG_INFO,
  455. _("Got IPv6 address %s\n"), val);
  456. if (!vpninfo->ip_info.addr6 && !vpninfo->ip_info.netmask6)
  457. inet_pton(AF_INET6, val, &ppp->out_ipv6_addr);
  458. }
  459. /* XX: The server's IP address(es) X-VPN-server-{IP,IPv6} are also
  460. * sent, but the utility of these is unclear. As remarked in oncp.c,
  461. * "this is a tunnel; having a gateway is meaningless." */
  462. return 0;
  463. }
  464. static int f5_configure(struct openconnect_info *vpninfo)
  465. {
  466. int ret;
  467. struct oc_text_buf *reqbuf = NULL;
  468. struct oc_vpn_option *cookie;
  469. char *profile_params = NULL;
  470. char *sid = NULL, *ur_z = NULL;
  471. int ipv4 = -1, ipv6 = -1, hdlc = 0;
  472. char *res_buf = NULL;
  473. if (!vpninfo->cookies) {
  474. /* XX: This will happen if authentication was separate/external */
  475. ret = internal_split_cookies(vpninfo, 1, "MRHSession");
  476. if (ret)
  477. return ret;
  478. }
  479. /* XX: parse "session timeout" cookie to get auth expiration */
  480. for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
  481. if (!strcmp(cookie->option, "F5_ST")) {
  482. int junk, start, dur;
  483. char c = 0;
  484. if (sscanf(cookie->value, "%dz%dz%dz%dz%d%c", &junk, &junk, &junk, &start, &dur, &c) >= 5
  485. && (c == 0 || c == 'z'))
  486. vpninfo->auth_expiration = start + dur;
  487. break;
  488. }
  489. }
  490. free(vpninfo->urlpath);
  491. vpninfo->urlpath = strdup("vdesk/vpn/index.php3?outform=xml&client_version=2.0");
  492. ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  493. if (ret < 0)
  494. goto out;
  495. ret = parse_profile(vpninfo, res_buf, ret, &profile_params);
  496. if (ret)
  497. goto out;
  498. vpn_progress(vpninfo, PRG_DEBUG,
  499. _("Got profile parameters '%s'\n"), profile_params);
  500. free(res_buf);
  501. res_buf = NULL;
  502. free(vpninfo->urlpath);
  503. if (asprintf(&vpninfo->urlpath, "vdesk/vpn/connect.php3?%s&outform=xml&client_version=2.0",
  504. profile_params) == -1) {
  505. ret = -ENOMEM;
  506. goto out;
  507. }
  508. ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  509. if (ret < 0)
  510. goto out;
  511. ret = parse_options(vpninfo, res_buf, ret, &sid, &ur_z, &ipv4, &ipv6, &hdlc);
  512. if (ret)
  513. goto out;
  514. vpn_progress(vpninfo, PRG_DEBUG,
  515. _("Got ipv4 %d ipv6 %d hdlc %d ur_Z '%s'\n"), ipv4, ipv6, hdlc, ur_z);
  516. if (ipv4 == -1)
  517. ipv4 = 0;
  518. if (ipv6 == -1)
  519. ipv6 = 0;
  520. /* XX: This buffer is used to initiate the connection over either TLS or DTLS.
  521. * Cookies are not needed for it to succeed, and can potentially grow without bound,
  522. * which would make it too big to fit in a single DTLS packet (ick, HTTP over DTLS).
  523. *
  524. * Don't blame me. I didn't design this.
  525. */
  526. reqbuf = vpninfo->ppp_dtls_connect_req;
  527. if (!reqbuf)
  528. reqbuf = buf_alloc();
  529. buf_truncate(reqbuf);
  530. buf_append(reqbuf, "GET /myvpn?sess=%s&hdlc_framing=%s&ipv4=%s&ipv6=%s&Z=%s&hostname=",
  531. sid, hdlc?"yes":"no", ipv4?"yes":"no", ipv6?"yes":"no", ur_z);
  532. buf_append_base64(reqbuf, vpninfo->localname, strlen(vpninfo->localname), 0);
  533. buf_append(reqbuf, " HTTP/1.1\r\n");
  534. struct oc_vpn_option *saved_cookies = vpninfo->cookies;
  535. vpninfo->cookies = NULL; /* hide cookies */
  536. http_common_headers(vpninfo, reqbuf);
  537. vpninfo->cookies = saved_cookies; /* restore cookies */
  538. buf_append(reqbuf, "\r\n");
  539. if (buf_error(reqbuf)) {
  540. vpn_progress(vpninfo, PRG_ERR,
  541. _("Error establishing F5 connection\n"));
  542. ret = buf_error(reqbuf);
  543. goto out;
  544. }
  545. vpninfo->ppp_dtls_connect_req = reqbuf;
  546. reqbuf = NULL;
  547. ret = openconnect_ppp_new(vpninfo, hdlc ? PPP_ENCAP_F5_HDLC : PPP_ENCAP_F5, ipv4, ipv6);
  548. out:
  549. free(res_buf);
  550. free(profile_params);
  551. free(sid);
  552. free(ur_z);
  553. buf_free(reqbuf);
  554. return ret;
  555. }
  556. int f5_connect(struct openconnect_info *vpninfo)
  557. {
  558. int ret = 0;
  559. if (!vpninfo->ppp) {
  560. /* Initial connection */
  561. ret = f5_configure(vpninfo);
  562. } else if (vpninfo->ppp->ppp_state != PPPS_DEAD) {
  563. /* TLS/DTLS reconnection with already-established PPP session
  564. * (PPP session will persist past reconnect.)
  565. */
  566. ret = ppp_reset(vpninfo);
  567. }
  568. if (ret) {
  569. err:
  570. openconnect_close_https(vpninfo, 0);
  571. return ret;
  572. }
  573. ret = ppp_tcp_should_connect(vpninfo);
  574. if (ret <= 0)
  575. goto err;
  576. ret = openconnect_open_https(vpninfo);
  577. if (ret)
  578. goto err;
  579. if (vpninfo->dump_http_traffic)
  580. dump_buf(vpninfo, '>', vpninfo->ppp_dtls_connect_req->data);
  581. ret = vpninfo->ssl_write(vpninfo, vpninfo->ppp_dtls_connect_req->data,
  582. vpninfo->ppp_dtls_connect_req->pos);
  583. if (ret < 0)
  584. goto err;
  585. struct oc_text_buf *resp_buf = buf_alloc();
  586. if (buf_error(resp_buf)) {
  587. ret = buf_free(resp_buf);
  588. goto err;
  589. }
  590. ret = process_http_response(vpninfo, 1, get_ip_address, resp_buf);
  591. buf_free(resp_buf);
  592. if (ret < 0)
  593. goto err;
  594. if (ret != 201 && ret != 200) {
  595. vpn_progress(vpninfo, PRG_ERR,
  596. _("Unexpected %d result from server\n"),
  597. ret);
  598. ret = (ret == 504) ? -EPERM : -EINVAL;
  599. goto err;
  600. }
  601. /* Trigger the first PPP negotiations and ensure the PPP state
  602. * is PPPS_ESTABLISH so that our mainloop knows we've started. */
  603. ppp_start_tcp_mainloop(vpninfo);
  604. monitor_fd_new(vpninfo, ssl);
  605. monitor_read_fd(vpninfo, ssl);
  606. monitor_except_fd(vpninfo, ssl);
  607. return 0;
  608. }
  609. int f5_bye(struct openconnect_info *vpninfo, const char *reason)
  610. {
  611. char *orig_path;
  612. char *res_buf = NULL;
  613. int ret;
  614. /* We need to close and reopen the HTTPS connection (to kill
  615. * the f5 tunnel) and submit a new HTTPS request to logout.
  616. */
  617. openconnect_close_https(vpninfo, 0);
  618. orig_path = vpninfo->urlpath;
  619. vpninfo->urlpath = strdup("vdesk/hangup.php3?hangup_error=1"); /* redirect segfaults without strdup */
  620. ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  621. free(vpninfo->urlpath);
  622. vpninfo->urlpath = orig_path;
  623. if (ret < 0)
  624. vpn_progress(vpninfo, PRG_ERR, _("Logout failed.\n"));
  625. else
  626. vpn_progress(vpninfo, PRG_INFO, _("Logout successful.\n"));
  627. free(res_buf);
  628. return ret;
  629. }
  630. int f5_dtls_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
  631. {
  632. char *line = (void *)pkt->data, *cr, *colon;
  633. int first = 1, status = -1;
  634. pkt->data[pkt->len] = 0;
  635. while (line && *line) {
  636. if (*line == '\n') {
  637. line++;
  638. continue;
  639. }
  640. cr = strchr(line, '\r');
  641. if (!cr)
  642. break;
  643. *cr = 0;
  644. if (first) {
  645. char junk, c = 0;
  646. if (sscanf(line, "HTTP/%c.%c %d%c", &junk, &junk, &status, &c) >= 3
  647. && (c == 0 || isspace(c))
  648. && status == 200) {
  649. first = 0;
  650. } else
  651. return (status >= 400 && status <= 499) ? -EPERM : -EINVAL;
  652. } else {
  653. colon = strchr(line, ':');
  654. if (colon) {
  655. *colon = 0;
  656. colon++;
  657. while (isspace(*colon))
  658. colon++;
  659. get_ip_address(vpninfo, line, colon);
  660. }
  661. }
  662. line = cr + 1;
  663. }
  664. return 1;
  665. }