fortinet.c 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  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. /* clthello/svrhello strings for Fortinet DTLS initialization.
  33. * NB: C string literals implicitly add a final \0 (which is correct for these).
  34. */
  35. static const char clthello[] = "GFtype\0clthello\0SVPNCOOKIE"; /* + cookie value + '\0' */
  36. static const char svrhello[] = "GFtype\0svrhello\0handshake"; /* + "ok"/"fail" + '\0' */
  37. void fortinet_common_headers(struct openconnect_info *vpninfo,
  38. struct oc_text_buf *buf)
  39. {
  40. char *orig_ua = vpninfo->useragent;
  41. /* XX: This is what openfortivpn uses */
  42. vpninfo->useragent = (char *)"Mozilla/5.0 SV1";
  43. http_common_headers(vpninfo, buf);
  44. vpninfo->useragent = orig_ua;
  45. /* XXX: Openfortivpn additionally sends the following
  46. * headers, even with GET requests, which should not be
  47. * necessary:
  48. buf_append(buf,
  49. "Accept: *" "/" "*\r\n"
  50. "Accept-Encoding: gzip, deflate, br\r\n"
  51. "Pragma: no-cache\r\n"
  52. "Cache-Control: no-store, no-cache, must-revalidate\r\n"
  53. "If-Modified-Since: Sat, 1 Jan 2000 00:00:00 GMT\r\n"
  54. "Content-Type: application/x-www-form-urlencoded\r\n"
  55. "Content-Length: 0\r\n");
  56. */
  57. }
  58. /* XX: consolidate with gpst.c version (differs only in '&' vs ',' as separator for input) */
  59. static int filter_opts(struct oc_text_buf *buf, const char *query, const char *incexc, int include)
  60. {
  61. const char query_sep = ',';
  62. const char *f, *endf, *eq;
  63. const char *found, *comma;
  64. for (f = query; *f; f=(*endf) ? endf+1 : endf) {
  65. endf = strchrnul(f, query_sep);
  66. eq = strchr(f, '=');
  67. if (!eq || eq > endf)
  68. eq = endf;
  69. for (found = incexc; *found; found=(*comma) ? comma+1 : comma) {
  70. comma = strchrnul(found, ',');
  71. if (!strncmp(found, f, MAX(comma-found, eq-f)))
  72. break;
  73. }
  74. if ((include && *found) || (!include && !*found)) {
  75. if (buf->pos && buf->data[buf->pos-1] != '?' && buf->data[buf->pos-1] != '&')
  76. buf_append(buf, "&");
  77. buf_append_bytes(buf, f, (int)(endf-f));
  78. }
  79. }
  80. return buf_error(buf);
  81. }
  82. int fortinet_obtain_cookie(struct openconnect_info *vpninfo)
  83. {
  84. int ret;
  85. struct oc_text_buf *req_buf = NULL;
  86. struct oc_auth_form *form = NULL;
  87. struct oc_form_opt *opt, *opt2;
  88. char *resp_buf = NULL, *realm = NULL, *tokeninfo_fields = NULL;
  89. req_buf = buf_alloc();
  90. if (buf_error(req_buf)) {
  91. ret = buf_error(req_buf);
  92. goto out;
  93. }
  94. ret = do_https_request(vpninfo, "GET", NULL, NULL, &resp_buf, NULL, HTTP_REDIRECT);
  95. if (ret < 0)
  96. goto out;
  97. /* XX: Fortinet's initial 'GET /' normally redirects to /remote/login.
  98. * If a valid, non-default "realm" is specified (~= usergroup or authgroup),
  99. * it will appear as a query parameter of the resulting URL, and we need to
  100. * capture and save it. That is, for example:
  101. * 'GET /MyRealmName' will redirect to '/remote/login?realm=MyRealmName'
  102. */
  103. if (vpninfo->urlpath) {
  104. for (realm = strchr(vpninfo->urlpath, '?'); realm && *++realm; realm=strchr(realm, '&')) {
  105. if (!strncmp(realm, "realm=", 6)) {
  106. const char *end = strchrnul(realm+1, '&');
  107. realm = strndup(realm+6, end-realm-6);
  108. vpn_progress(vpninfo, PRG_INFO, _("Got login realm '%s'\n"), realm);
  109. break;
  110. }
  111. }
  112. }
  113. /* XX: Fortinet HTML forms *seem* like they should be about as easy to follow
  114. * as Juniper HTML forms, but some redirects use Javascript EXCLUSIVELY (no
  115. * 'Location' header). Also, a failed login returns the misleading HTTP status
  116. * "405 Method Not Allowed", rather than 403/401, and HTTP status 401 is used to
  117. * signal an HTML-form-based mode of presenting a 2FA challenge.
  118. *
  119. * So we just build a static form (username and password) to start.
  120. */
  121. form = calloc(1, sizeof(*form));
  122. if (!form) {
  123. nomem:
  124. ret = -ENOMEM;
  125. goto out;
  126. }
  127. form->auth_id = strdup("_login");
  128. if (!form->auth_id)
  129. goto nomem;
  130. opt = form->opts = calloc(1, sizeof(*opt));
  131. if (!opt)
  132. goto nomem;
  133. opt->label = strdup("Username: ");
  134. opt->name = strdup("username");
  135. opt->type = OC_FORM_OPT_TEXT;
  136. opt2 = opt->next = calloc(1, sizeof(*opt2));
  137. if (!opt2)
  138. goto nomem;
  139. opt2->label = strdup("Password: ");
  140. opt2->name = strdup("credential");
  141. opt2->type = OC_FORM_OPT_PASSWORD;
  142. free(vpninfo->urlpath);
  143. vpninfo->urlpath = strdup("remote/logincheck");
  144. /* XX: submit form repeatedly until success? */
  145. for (;;) {
  146. ret = process_auth_form(vpninfo, form);
  147. if (ret == OC_FORM_RESULT_CANCELLED || ret < 0)
  148. goto out;
  149. /* generate token code if specified */
  150. ret = do_gen_tokencode(vpninfo, form);
  151. if (ret) {
  152. vpn_progress(vpninfo, PRG_ERR, _("Failed to generate OTP tokencode; disabling token\n"));
  153. vpninfo->token_bypassed = 1;
  154. goto out;
  155. }
  156. buf_truncate(req_buf);
  157. append_form_opts(vpninfo, form, req_buf);
  158. buf_append(req_buf, "&realm=%s", realm ?: ""); /* XX: already URL-escaped */
  159. if (!tokeninfo_fields) {
  160. /* "normal" form (fields 'username', 'credential') */
  161. buf_append(req_buf, "&ajax=1&just_logged_in=1");
  162. } else {
  163. /* 2FA form (fields 'username', 'code', and a bunch of values
  164. * from the previous response which we mindlessly parrot back)
  165. */
  166. buf_append(req_buf, "&code2=&%s", tokeninfo_fields);
  167. free(tokeninfo_fields);
  168. tokeninfo_fields = NULL;
  169. }
  170. if ((ret = buf_error(req_buf)))
  171. goto out;
  172. /* XX: Disable HTTP auth, because Fortinet uses 401 status to indicate HTML-type 2FA challenge */
  173. int try_http_auth = vpninfo->try_http_auth;
  174. vpninfo->try_http_auth = 0;
  175. ret = do_https_request(vpninfo, "POST", "application/x-www-form-urlencoded",
  176. req_buf, &resp_buf, NULL, HTTP_BODY_ON_ERROR);
  177. vpninfo->try_http_auth = try_http_auth;
  178. /* If we got SVPNCOOKIE, then we're done. */
  179. struct oc_vpn_option *cookie;
  180. for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
  181. if (!strcmp(cookie->option, "SVPNCOOKIE")) {
  182. free(vpninfo->cookie);
  183. if (asprintf(&vpninfo->cookie, "SVPNCOOKIE=%s", cookie->value) < 0)
  184. goto nomem;
  185. ret = 0;
  186. goto out;
  187. }
  188. }
  189. /* XX: We got 200 status, but no SVPNCOOKIE. tokeninfo-type 2FA? */
  190. if (ret > 0 &&
  191. !strncmp(resp_buf, "ret=", 4) && strstr(resp_buf, ",tokeninfo=")) {
  192. const char *prompt;
  193. struct oc_text_buf *tokeninfo_buf = buf_alloc();
  194. /* Hide 'username' field */
  195. opt->type = OC_FORM_OPT_HIDDEN;
  196. free(opt2->label);
  197. free(opt2->_value);
  198. opt2->label = opt2->_value = NULL;
  199. /* Change 'credential' field to 'code'. */
  200. opt2->_value = NULL;
  201. opt2->name = strdup("code");
  202. opt2->label = strdup("Code: ");
  203. if (!can_gen_tokencode(vpninfo, form, opt2))
  204. opt2->type = OC_FORM_OPT_TOKEN;
  205. else
  206. opt2->type = OC_FORM_OPT_PASSWORD;
  207. /* Change 'auth_id' to '_challenge'. */
  208. free(form->auth_id);
  209. if (!(form->auth_id = strdup("_challenge")))
  210. goto nomem;
  211. /* Save a bunch of values to parrot back */
  212. filter_opts(tokeninfo_buf, resp_buf, "reqid,polid,grp,portal,peer,magic", 1);
  213. if ((ret = buf_error(tokeninfo_buf)))
  214. goto out;
  215. free(tokeninfo_fields);
  216. tokeninfo_fields = tokeninfo_buf->data;
  217. tokeninfo_buf->data = NULL;
  218. buf_free(tokeninfo_buf);
  219. if ((prompt = strstr(resp_buf, ",chal_msg="))) {
  220. const char *end = strchrnul(prompt, ',');
  221. prompt += 10;
  222. free(form->message);
  223. form->message = strndup(prompt, end-prompt);
  224. }
  225. }
  226. /* XX: We got 401 response with HTML body. HTML-type 2FA? */
  227. else if (ret == -EPERM && resp_buf) {
  228. xmlDocPtr doc = NULL;
  229. xmlNode *node;
  230. char *url = internal_get_url(vpninfo);
  231. if (!url)
  232. goto nomem;
  233. /* XX: HTML body should contain a "normal" HTML form with hidden fields
  234. * including 'username', 'magic', 'reqid', 'grpid' (similar to the tokeninfo-type
  235. * 2FA bove), and a password field named 'credential'.
  236. */
  237. doc = htmlReadMemory(resp_buf, strlen(resp_buf), url, NULL,
  238. HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING|HTML_PARSE_NONET);
  239. free(url);
  240. node = find_form_node(doc);
  241. if (node) {
  242. free_auth_form(form);
  243. form = parse_form_node(vpninfo, node, NULL, can_gen_tokencode);
  244. if (!form)
  245. goto no_html_form;
  246. } else {
  247. no_html_form:
  248. xmlFreeDoc(doc);
  249. ret = -EINVAL;
  250. goto out;
  251. }
  252. }
  253. }
  254. out:
  255. free(realm);
  256. if (resp_buf)
  257. free(resp_buf);
  258. if (form)
  259. free_auth_form(form);
  260. free(tokeninfo_fields);
  261. buf_free(req_buf);
  262. return ret;
  263. }
  264. static int parse_split_routes(struct openconnect_info *vpninfo, xmlNode *split_tunnel_info,
  265. struct oc_vpn_option *new_opts, struct oc_ip_info *new_ip_info)
  266. {
  267. int negate = 0, ret = 0;
  268. int ip_version = !strcmp((char *)split_tunnel_info->parent->name, "ipv6") ? 6 : 4;
  269. char *s = NULL, *s2 = NULL;
  270. if (!xmlnode_get_prop(split_tunnel_info, "negate", &s))
  271. negate = atoi(s);
  272. for (xmlNode *x = split_tunnel_info->children; x; x=x->next) {
  273. if (xmlnode_is_named(x, "addr")) {
  274. if (!xmlnode_get_prop(x, ip_version == 6 ? "ipv6" : "ip", &s) &&
  275. !xmlnode_get_prop(x, ip_version == 6 ? "prefix-len" : "mask", &s2) &&
  276. s && s2 && *s && *s2) {
  277. struct oc_split_include *inc = malloc(sizeof(*inc));
  278. char *route = NULL;
  279. if (!inc || asprintf(&route, "%s/%s", s, s2) == -1) {
  280. free(route);
  281. free(inc);
  282. free_optlist(new_opts);
  283. free_split_routes(new_ip_info);
  284. ret = -ENOMEM;
  285. goto out;
  286. }
  287. if (negate) {
  288. vpn_progress(vpninfo, PRG_INFO, _("Got IPv%d exclude route %s\n"), ip_version, route);
  289. inc->route = add_option_steal(&new_opts, "split-exclude", &route);
  290. inc->next = new_ip_info->split_excludes;
  291. new_ip_info->split_excludes = inc;
  292. } else {
  293. vpn_progress(vpninfo, PRG_INFO, _("Got IPv%d route %s\n"), ip_version, route);
  294. inc->route = add_option_steal(&new_opts, "split-include", &route);
  295. inc->next = new_ip_info->split_includes;
  296. new_ip_info->split_includes = inc;
  297. }
  298. /* XX: static analyzer doesn't realize that add_option_steal will steal route's reference, so... */
  299. free(route);
  300. }
  301. }
  302. }
  303. out:
  304. free(s);
  305. free(s2);
  306. return ret;
  307. }
  308. /* Parse this:
  309. <?xml version="1.0" encoding="utf-8"?>
  310. <sslvpn-tunnel ver="2" dtls="1" patch="1">
  311. <dtls-config heartbeat-interval="10" heartbeat-fail-count="10" heartbeat-idle-timeout="10" client-hello-timeout="10"/>
  312. <tunnel-method value="ppp"/>
  313. <tunnel-method value="tun"/>
  314. <fos platform="FG100E" major="5" minor="06" patch="6" build="1630" branch="1630"/>
  315. <auth-ses check-src-ip='1' tun-connect-without-reauth='1' tun-user-ses-timeout='240' />
  316. <client-config save-password="off" keep-alive="on" auto-connect="off"/>
  317. <ipv4>
  318. <dns ip="1.1.1.1"/>
  319. <dns ip="8.8.8.8" domain="foo.com"/>
  320. <split-dns domains='mydomain1.local,mydomain2.local' dnsserver1='10.10.10.10' dnsserver2='10.10.10.11' />
  321. <assigned-addr ipv4="172.16.1.1"/>
  322. <split-tunnel-info>
  323. <addr ip="10.11.10.10" mask="255.255.255.255"/>
  324. <addr ip="10.11.1.0" mask="255.255.255.0"/>
  325. </split-tunnel-info>
  326. <split-tunnel-info negate="1">
  327. <addr ip="1.2.3.4" mask="255.255.255.255"/>
  328. </split-tunnel-info>
  329. </ipv4>
  330. <ipv6>
  331. <assigned-addr ipv6='fdff:ffff::1' prefix-len='120'/>
  332. <split-tunnel-info>
  333. <addr ipv6='fdff:ffff::' prefix-len='120'/>
  334. </split-tunnel-info>
  335. <split-tunnel-info negate="1">
  336. <addr ipv6='2011:abcd::' prefix-len='32'/>
  337. </split-tunnel-info>
  338. </ipv6>
  339. <idle-timeout val="3600"/>
  340. <auth-timeout val="18000"/>
  341. </sslvpn-tunnel>
  342. */
  343. static int parse_fortinet_xml_config(struct openconnect_info *vpninfo, char *buf, int len)
  344. {
  345. xmlNode *xml_node, *x;
  346. xmlDocPtr xml_doc;
  347. int ret = 0, n_dns = 0;
  348. char *s = NULL, *s2 = NULL;
  349. int reconnect_after_drop = -1;
  350. struct oc_text_buf *domains = NULL;
  351. if (!buf || !len)
  352. return -EINVAL;
  353. xml_doc = xmlReadMemory(buf, len, NULL, NULL,
  354. XML_PARSE_NOERROR|XML_PARSE_RECOVER);
  355. if (!xml_doc) {
  356. vpn_progress(vpninfo, PRG_ERR,
  357. _("Failed to parse Fortinet config XML\n"));
  358. vpn_progress(vpninfo, PRG_DEBUG,
  359. _("Response was:%s\n"), buf);
  360. return -EINVAL;
  361. }
  362. xml_node = xmlDocGetRootElement(xml_doc);
  363. if (!xml_node || !xmlnode_is_named(xml_node, "sslvpn-tunnel"))
  364. return -EINVAL;
  365. struct oc_vpn_option *new_opts = NULL;
  366. struct oc_ip_info new_ip_info = {};
  367. domains = buf_alloc();
  368. if (vpninfo->dtls_state == DTLS_NOSECRET &&
  369. !xmlnode_get_prop(xml_node, "dtls", &s) && atoi(s)) {
  370. udp_sockaddr(vpninfo, vpninfo->port); /* XX: DTLS always uses same port as TLS? */
  371. vpn_progress(vpninfo, PRG_INFO, _("DTLS is enabled on port %d\n"), vpninfo->port);
  372. vpninfo->dtls_state = DTLS_SECRET;
  373. /* This doesn't mean it actually will; it means that we can at least *try* */
  374. vpninfo->dtls12 = 1;
  375. }
  376. for (xml_node = xml_node->children; xml_node; xml_node=xml_node->next) {
  377. if (xmlnode_is_named(xml_node, "auth-timeout") && !xmlnode_get_prop(xml_node, "val", &s))
  378. vpninfo->auth_expiration = time(NULL) + atol(s);
  379. else if (xmlnode_is_named(xml_node, "idle-timeout") && !xmlnode_get_prop(xml_node, "val", &s)) {
  380. int sec = vpninfo->idle_timeout = atoi(s);
  381. vpn_progress(vpninfo, PRG_INFO, _("Idle timeout is %d minutes.\n"), sec/60);
  382. } else if (xmlnode_is_named(xml_node, "dtls-config") && !xmlnode_get_prop(xml_node, "heartbeat-interval", &s)) {
  383. int sec = atoi(s);
  384. if (sec && !vpninfo->dtls_times.dpd)
  385. vpninfo->dtls_times.dpd = vpninfo->ssl_times.dpd = sec;
  386. } else if (xmlnode_is_named(xml_node, "auth-ses")) {
  387. /* These settings were apparently added in v6.2.1 of the Fortigate server,
  388. * (see https://docs.fortinet.com/document/fortigate/6.2.1/cli-reference/281620/vpn-ssl-settings)
  389. * and seem to control the possibility of reconnecting after a dropped connection.
  390. * See discussion at https://gitlab.com/openconnect/openconnect/-/issues/297#note_664686767
  391. */
  392. int check_ip_src = -1, dropped_session_cleanup = -1;
  393. if (!xmlnode_get_prop(xml_node, "tun-connect-without-reauth", &s)) {
  394. reconnect_after_drop = atoi(s);
  395. if (reconnect_after_drop) {
  396. if (!xmlnode_get_prop(xml_node, "check-src-ip", &s))
  397. check_ip_src = atoi(s);
  398. if (!xmlnode_get_prop(xml_node, "tun-user-ses-timeout", &s))
  399. dropped_session_cleanup = atoi(s);
  400. vpn_progress(vpninfo, PRG_INFO,
  401. _("Server reports that reconnect-after-drop is allowed within %d seconds, %s\n"),
  402. dropped_session_cleanup,
  403. check_ip_src ? _("but only from the same source IP address") : _("even if source IP address changes"));
  404. } else
  405. vpn_progress(vpninfo, PRG_INFO,
  406. _("Server reports that reconnect-after-drop is not allowed. OpenConnect will not\n"
  407. "be able to reconnect if dead peer is detected. If reconnection DOES work,\n"
  408. "please report to <%s>\n"),
  409. "openconnect-devel@lists.infradead.org");
  410. }
  411. } else if (xmlnode_is_named(xml_node, "fos")) {
  412. char platform[80], *p = platform, *e = platform + 80;
  413. if (!xmlnode_get_prop(xml_node, "platform", &s)) {
  414. p+=snprintf(p, e-p, "%s", s);
  415. if (!xmlnode_get_prop(xml_node, "major", &s)) p+=snprintf(p, e-p, " v%s", s);
  416. if (!xmlnode_get_prop(xml_node, "minor", &s)) p+=snprintf(p, e-p, ".%s", s);
  417. if (!xmlnode_get_prop(xml_node, "patch", &s)) p+=snprintf(p, e-p, ".%s", s);
  418. if (!xmlnode_get_prop(xml_node, "build", &s)) p+=snprintf(p, e-p, " build %s", s);
  419. if (!xmlnode_get_prop(xml_node, "branch", &s)) p+=snprintf(p, e-p, " branch %s", s);
  420. if (!xmlnode_get_prop(xml_node, "mr_num", &s)) snprintf(p, e-p, " mr_num %s", s);
  421. vpn_progress(vpninfo, PRG_INFO,
  422. _("Reported platform is %s\n"), platform);
  423. }
  424. } else if (xmlnode_is_named(xml_node, "ipv4")) {
  425. for (x = xml_node->children; x; x=x->next) {
  426. if (xmlnode_is_named(x, "assigned-addr") && !xmlnode_get_prop(x, "ipv4", &s)) {
  427. vpn_progress(vpninfo, PRG_INFO, _("Got Legacy IP address %s\n"), s);
  428. new_ip_info.addr = add_option_steal(&new_opts, "ipaddr", &s);
  429. } else if (xmlnode_is_named(x, "dns")) {
  430. if (!xmlnode_get_prop(x, "domain", &s) && s && *s) {
  431. vpn_progress(vpninfo, PRG_INFO, _("Got search domain %s\n"), s);
  432. buf_append(domains, "%s ", s);
  433. }
  434. if (!xmlnode_get_prop(x, "ip", &s) && s && *s) {
  435. vpn_progress(vpninfo, PRG_INFO, _("Got IPv%d DNS server %s\n"), 4, s);
  436. if (n_dns < 3) new_ip_info.dns[n_dns++] = add_option_steal(&new_opts, "DNS", &s);
  437. }
  438. } else if (xmlnode_is_named(x, "split-dns")) {
  439. int ii;
  440. if (!xmlnode_get_prop(x, "domains", &s) && s && *s)
  441. vpn_progress(vpninfo, PRG_ERR, _("WARNING: Got split-DNS domains %s (not yet implemented)\n"), s);
  442. for (ii=1; ii<10; ii++) {
  443. char propname[] = "dnsserver0";
  444. propname[9] = '0' + ii;
  445. if (!xmlnode_get_prop(x, propname, &s) && s && *s)
  446. vpn_progress(vpninfo, PRG_ERR, _("WARNING: Got split-DNS server %s (not yet implemented)\n"), s);
  447. else
  448. break;
  449. }
  450. } else if (xmlnode_is_named(x, "split-tunnel-info")) {
  451. ret = parse_split_routes(vpninfo, x, new_opts, &new_ip_info);
  452. if (ret < 0)
  453. goto out;
  454. }
  455. }
  456. } else if (xmlnode_is_named(xml_node, "ipv6")) {
  457. for (x = xml_node->children; x; x=x->next) {
  458. if (xmlnode_is_named(x, "assigned-addr") && !xmlnode_get_prop(x, "ipv6", &s)) {
  459. if (!xmlnode_get_prop(x, "prefix-len", &s2)) {
  460. char *a;
  461. if (asprintf(&a, "%s/%s", s, s2) < 0) {
  462. ret = -ENOMEM;
  463. goto out;
  464. }
  465. vpn_progress(vpninfo, PRG_INFO, _("Got IPv6 address %s\n"), a);
  466. if (!vpninfo->disable_ipv6)
  467. new_ip_info.netmask6 = add_option_steal(&new_opts, "ipaddr6", &a);
  468. free(a);
  469. } else {
  470. vpn_progress(vpninfo, PRG_INFO, _("Got IPv6 address %s\n"), s);
  471. if (!vpninfo->disable_ipv6)
  472. new_ip_info.addr6 = add_option_steal(&new_opts, "ipaddr6", &s);
  473. }
  474. } else if (xmlnode_is_named(x, "dns")) {
  475. if (!xmlnode_get_prop(x, "domain", &s) && s && *s) {
  476. vpn_progress(vpninfo, PRG_INFO, _("Got search domain %s\n"), s);
  477. buf_append(domains, "%s ", s);
  478. }
  479. if (!xmlnode_get_prop(x, "ipv6", &s) && s && *s) {
  480. vpn_progress(vpninfo, PRG_INFO, _("Got IPv%d DNS server %s\n"), 6, s);
  481. if (n_dns < 3) new_ip_info.dns[n_dns++] = add_option_steal(&new_opts, "DNS", &s);
  482. }
  483. } else if (xmlnode_is_named(x, "split-dns")) {
  484. int ii;
  485. if (!xmlnode_get_prop(x, "domains", &s) && s && *s)
  486. vpn_progress(vpninfo, PRG_ERR, _("WARNING: Got split-DNS domains %s (not yet implemented)\n"), s);
  487. for (ii=1; ii<10; ii++) {
  488. char propname[] = "dnsserver0";
  489. propname[9] = '0' + ii;
  490. if (!xmlnode_get_prop(x, propname, &s) && s && *s)
  491. vpn_progress(vpninfo, PRG_ERR, _("WARNING: Got split-DNS server %s (not yet implemented)\n"), s);
  492. else
  493. break;
  494. }
  495. } else if (xmlnode_is_named(x, "split-tunnel-info")) {
  496. ret = parse_split_routes(vpninfo, x, new_opts, &new_ip_info);
  497. if (ret != 0)
  498. goto out;
  499. }
  500. }
  501. }
  502. }
  503. if (reconnect_after_drop < 0) {
  504. vpn_progress(vpninfo, PRG_ERR,
  505. _("WARNING: Fortinet server does not specifically enable or disable reconnection\n"
  506. " without reauthentication. If automatic reconnection does work, please\n"
  507. " report results to <%s>\n"),
  508. "openconnect-devel@lists.infradead.org");
  509. }
  510. if (reconnect_after_drop == -1)
  511. vpn_progress(vpninfo, PRG_ERR,
  512. _("Server did not send <auth-ses tun-connect-without-reauth=\"0/1\"/>. OpenConnect will\n"
  513. "probably not be able to reconnect if dead peer is detected. If reconnection DOES,\n"
  514. "work please report to <%s>\n"),
  515. "openconnect-devel@lists.infradead.org");
  516. if (new_ip_info.addr) {
  517. if (new_ip_info.split_includes)
  518. vpn_progress(vpninfo, PRG_INFO, _("Received split routes; not setting default Legacy IP route\n"));
  519. else {
  520. vpn_progress(vpninfo, PRG_INFO, _("No split routes received; setting default Legacy IP route\n"));
  521. new_ip_info.netmask = add_option_dup(&new_opts, "full-netmask", "0.0.0.0", -1);
  522. }
  523. }
  524. if (buf_error(domains) == 0 && domains->pos > 0) {
  525. domains->data[domains->pos-1] = '\0';
  526. new_ip_info.domain = add_option_steal(&new_opts, "search", &domains->data);
  527. }
  528. ret = install_vpn_opts(vpninfo, new_opts, &new_ip_info);
  529. if (ret) {
  530. free_optlist(new_opts);
  531. free_split_routes(&new_ip_info);
  532. vpn_progress(vpninfo, PRG_ERR,
  533. _("Failed to find VPN options\n"));
  534. vpn_progress(vpninfo, PRG_DEBUG,
  535. _("Response was:%s\n"), buf);
  536. }
  537. out:
  538. xmlFreeDoc(xml_doc);
  539. buf_free(domains);
  540. free(s);
  541. free(s2);
  542. return ret;
  543. }
  544. static int fortinet_configure(struct openconnect_info *vpninfo)
  545. {
  546. char *res_buf = NULL;
  547. struct oc_text_buf *reqbuf = NULL;
  548. struct oc_vpn_option *svpncookie = NULL;
  549. int ret;
  550. /* XXX: We should use check_address_sanity to verify that addresses haven't
  551. changed on a reconnect, except that:
  552. 1) We haven't yet been able to test fully on a Fortinet
  553. server that actually allows reconnects
  554. 2) The evidence we do have suggests that Fortinet servers which *do* allow
  555. reconnects nevertheless *do not* allow us to redo the configuration requests
  556. without invalidating the cookie. So reconnects *must* use only ppp_reset(),
  557. rather than calling fortinet_configure(), to redo the PPP tunnel setup. See
  558. https://gitlab.com/openconnect/openconnect/-/issues/235#note_552995833
  559. */
  560. if (!vpninfo->cookies) {
  561. /* XX: This will happen if authentication was separate/external */
  562. ret = internal_split_cookies(vpninfo, 1, "SVPNCOOKIE");
  563. if (ret)
  564. return ret;
  565. }
  566. for (svpncookie = vpninfo->cookies; svpncookie; svpncookie = svpncookie->next)
  567. if (!strcmp(svpncookie->option, "SVPNCOOKIE"))
  568. break;
  569. if (!svpncookie) {
  570. vpn_progress(vpninfo, PRG_ERR, _("No cookie named SVPNCOOKIE.\n"));
  571. ret = -EINVAL;
  572. goto out;
  573. }
  574. free(vpninfo->urlpath);
  575. /* Fetch the connection options in XML format */
  576. vpninfo->urlpath = strdup("remote/fortisslvpn_xml");
  577. ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  578. if (ret < 0) {
  579. if (ret == -EPERM) {
  580. /* XXX: Forticlient and Openfortivpn fetch the legacy HTTP configuration.
  581. * FortiOS 4 was the last version to send the legacy HTTP configuration.
  582. * FortiOS 5 and later send the current XML configuration.
  583. * We clearly do not need to support FortiOS 4 anymore.
  584. *
  585. * Yet we keep this code around in order to get a sanity check about
  586. * whether the SVPNCOOKIE is still valid/alive, until we are sure we've
  587. * worked out the weirdness with reconnects.
  588. */
  589. vpninfo->urlpath = strdup("remote/fortisslvpn");
  590. int ret2 = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  591. if (ret2 > 0)
  592. vpn_progress(vpninfo, PRG_ERR,
  593. _("Ancient Fortinet server (<v5?) only supports ancient HTML config, which is not implemented by OpenConnect.\n"));
  594. else
  595. vpn_progress(vpninfo, PRG_ERR,
  596. _("Fortinet server is rejecting request for connection options. This\n"
  597. "has been observed after reconnection in some cases. Please report to\n"
  598. "<%s>, or see the discussions on\n"
  599. "%s and\n"
  600. "%s.\n"),
  601. "openconnect-devel@lists.infradead.org",
  602. "https://gitlab.com/openconnect/openconnect/-/issues/297",
  603. "https://gitlab.com/openconnect/openconnect/-/issues/298");
  604. }
  605. goto out;
  606. } else if (ret == 0) {
  607. /* A redirect to /remote/login also indicates that the auth session/cookie
  608. * is no longer valid, and appears to occur only on older FortiGate
  609. * versions.
  610. *
  611. * XX: See do_https_request() for why ret==0 can only happen
  612. * if there was a successful-but-unfetched redirect.
  613. */
  614. if (vpninfo->urlpath && !strncmp(vpninfo->urlpath, "remote/login", 12))
  615. ret = -EPERM;
  616. else
  617. ret = -EINVAL;
  618. goto out;
  619. }
  620. ret = parse_fortinet_xml_config(vpninfo, res_buf, ret);
  621. if (ret)
  622. goto out;
  623. reqbuf = vpninfo->ppp_tls_connect_req;
  624. if (!reqbuf)
  625. reqbuf = buf_alloc();
  626. buf_truncate(reqbuf);
  627. buf_append(reqbuf, "GET /remote/sslvpn-tunnel HTTP/1.1\r\n");
  628. fortinet_common_headers(vpninfo, reqbuf);
  629. buf_append(reqbuf, "\r\n");
  630. if ((ret = buf_error(reqbuf))) {
  631. buf_err:
  632. vpn_progress(vpninfo, PRG_ERR,
  633. _("Error establishing Fortinet connection\n"));
  634. goto out;
  635. }
  636. vpninfo->ppp_tls_connect_req = reqbuf;
  637. reqbuf = NULL;
  638. reqbuf = vpninfo->ppp_dtls_connect_req;
  639. if (!reqbuf)
  640. reqbuf = buf_alloc();
  641. buf_truncate(reqbuf);
  642. buf_append_be16(reqbuf, 2 + sizeof(clthello) + strlen(svpncookie->value) + 1); /* length */
  643. buf_append_bytes(reqbuf, clthello, sizeof(clthello));
  644. buf_append(reqbuf, "%s%c", svpncookie->value, 0);
  645. if ((ret = buf_error(reqbuf)))
  646. goto buf_err;
  647. vpninfo->ppp_dtls_connect_req = reqbuf;
  648. reqbuf = NULL;
  649. int ipv4 = !!vpninfo->ip_info.addr;
  650. int ipv6 = !!(vpninfo->ip_info.addr6 || vpninfo->ip_info.netmask6);
  651. ret = openconnect_ppp_new(vpninfo, PPP_ENCAP_FORTINET, ipv4, ipv6);
  652. out:
  653. buf_free(reqbuf);
  654. free(res_buf);
  655. return ret;
  656. }
  657. int fortinet_connect(struct openconnect_info *vpninfo)
  658. {
  659. int ret = 0;
  660. ret = fortinet_configure(vpninfo);
  661. if (ret) {
  662. err:
  663. openconnect_close_https(vpninfo, 0);
  664. return ret;
  665. }
  666. ret = ppp_tcp_should_connect(vpninfo);
  667. if (ret <= 0)
  668. goto err;
  669. /* XX: Openfortivpn closes and reopens the HTTPS connection here, and
  670. * also sends 'Host: sslvpn' (rather than the true hostname). Neither
  671. * appears to be necessary, and either might prevent connecting to
  672. * a vhost-based Fortinet server.
  673. */
  674. ret = openconnect_open_https(vpninfo);
  675. if (ret)
  676. goto err;
  677. if (vpninfo->dump_http_traffic)
  678. dump_buf(vpninfo, '>', vpninfo->ppp_tls_connect_req->data);
  679. ret = vpninfo->ssl_write(vpninfo, vpninfo->ppp_tls_connect_req->data,
  680. vpninfo->ppp_tls_connect_req->pos);
  681. if (ret < 0) {
  682. openconnect_close_https(vpninfo, 0);
  683. goto err;
  684. }
  685. /* XX: If this connection request succeeds, no HTTP response appears.
  686. * We just start sending our encapsulated PPP configuration packets.
  687. * However, if the request FAILS, it WILL send an HTTP response.
  688. * We handle that in the PPP mainloop.
  689. *
  690. * Don't blame me. I didn't design this.
  691. */
  692. vpninfo->ppp->check_http_response = 1;
  693. /* Trigger the first PPP negotiations and ensure the PPP state
  694. * is PPPS_ESTABLISH so that ppp_tcp_mainloop() knows we've started. */
  695. ppp_start_tcp_mainloop(vpninfo);
  696. /* XX: Some Fortinet servers can't cope with reconnect, which means
  697. * there's absolutely no point in trying to opportunistically do
  698. * DTLS after this point. Can we detect that, and disable DTLS?
  699. * I think it's relatively harmless because the auth packet over
  700. * DTLS will fail anyway, so we'll never make it past DTLS_CONNECTED
  701. * to DTLS_ESTABLISHED and never give up on the existing TCP link
  702. * but it's still a waste of time and resources trying to do it
  703. * at all. */
  704. monitor_fd_new(vpninfo, ssl);
  705. monitor_read_fd(vpninfo, ssl);
  706. monitor_except_fd(vpninfo, ssl);
  707. return 0;
  708. }
  709. int fortinet_dtls_catch_svrhello(struct openconnect_info *vpninfo, struct pkt *pkt)
  710. {
  711. char *const buf = (void *)pkt->data;
  712. const int len = pkt->len;
  713. buf[len] = 0;
  714. if (load_be16(buf) != len || len < sizeof(svrhello) + 2 ||
  715. memcmp(buf + 2, svrhello, sizeof(svrhello))) {
  716. vpn_progress(vpninfo, PRG_ERR,
  717. _("Did not receive expected svrhello response.\n"));
  718. dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)buf, len);
  719. disable:
  720. dtls_close(vpninfo);
  721. vpninfo->dtls_state = DTLS_DISABLED;
  722. return -EINVAL;
  723. }
  724. if (strncmp("ok", buf + 2 + sizeof(svrhello),
  725. len - 2 - sizeof(svrhello))) {
  726. vpn_progress(vpninfo, PRG_ERR,
  727. _("svrhello status was \"%.*s\" rather than \"ok\"\n"),
  728. (int)(len - 2 - sizeof(svrhello)),
  729. buf + 2 + sizeof(svrhello));
  730. goto disable;
  731. }
  732. /* XX: The 'ok' packet might get dropped, and the server won't resend
  733. * it when we resend the GET request. What will happen in that case
  734. * is it'll just keep sending PPP frames. If we detect a PPP frame
  735. * we should take that as 'success' too. Bonus points for actually
  736. * feeding it to the PPP code to process too, but dropping it *ought*
  737. * to be OK. */
  738. return 1;
  739. }
  740. int fortinet_bye(struct openconnect_info *vpninfo, const char *reason)
  741. {
  742. char *orig_path;
  743. char *res_buf=NULL;
  744. int ret;
  745. /* XX: handle clean PPP termination?
  746. ppp_bye(vpninfo); */
  747. /* We need to close and reopen the HTTPS connection (to kill
  748. * the fortinet tunnel) and submit a new HTTPS request to logout.
  749. */
  750. openconnect_close_https(vpninfo, 0);
  751. orig_path = vpninfo->urlpath;
  752. vpninfo->urlpath = strdup("remote/logout");
  753. ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
  754. free(vpninfo->urlpath);
  755. vpninfo->urlpath = orig_path;
  756. if (ret < 0)
  757. vpn_progress(vpninfo, PRG_ERR, _("Logout failed.\n"));
  758. else
  759. vpn_progress(vpninfo, PRG_INFO, _("Logout successful.\n"));
  760. free(res_buf);
  761. return ret;
  762. }