123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- // SPDX-License-Identifier: LGPL-2.1-or-later
- // Author: vimacs <vimacs.hacks@gmail.com>
- #include <config.h>
- #include "openconnect-internal.h"
- #include <assert.h>
- #include <libxml/tree.h>
- static int h3c_request_user_auth(struct openconnect_info *vpninfo, char *username,
- char *password)
- {
- struct oc_auth_form f;
- struct oc_form_opt o[2];
- int ret;
- memset(&f, 0, sizeof(f));
- memset(o, 0, sizeof(o));
- f.auth_id = (char *)"H3C user";
- f.message = _("Enter user credentials:");
- if (true) {
- f.opts = &o[0];
- o[0].next = NULL; /* Again, for now */
- o[0].type = OC_FORM_OPT_TEXT;
- o[0].name = (char *)"username";
- o[0].label = (char *)_("Username:");
- }
- if (true) {
- /* Might be referenced from o[0] or directly from f.opts */
- o[0].next = &o[1];
- o[1].type = OC_FORM_OPT_PASSWORD;
- o[1].name = (char *)"password";
- o[1].label = (char *)_("Password:");
- }
- ret = process_auth_form(vpninfo, &f);
- if (ret != 0) {
- return ret;
- }
- if (o[0]._value) {
- // FIXME: Yes, it's unsafe...
- strcpy(username, o[0]._value);
- free_pass(&o[0]._value);
- }
- if (o[1]._value) {
- strcpy(password, o[1]._value);
- free_pass(&o[1]._value);
- }
- return 0;
- }
- static int h3c_get_vpn_param_from_headers(struct openconnect_info *vpninfo, char *k, char *v)
- {
- bool set_value = true;
- if (strcmp(k, "IPADDRESS") == 0) {
- vpninfo->ip_info.addr = strdup(v);
- } else if (strcmp(k, "SUBNETMASK") == 0) {
- if (strcmp(v, "24") == 0) {
- vpninfo->ip_info.netmask = strdup("255.255.255.0");
- } else {
- vpn_progress(vpninfo, PRG_ERR, "Unknown net mask %s\n", v);
- }
- } else if (strcmp(k, "GATEWAY") == 0) {
- vpninfo->ip_info.gateway_addr = strdup(v);
- } else if (strcmp(k, "ROUTES") == 0) {
- // ROUTES: ip/mask;ip/mask;...;ip/mask
- const char *vp = v;
- while (*vp != 0) {
- const char *scanpos = vp;
- while (*scanpos != 0 && *scanpos != ';') {
- ++scanpos;
- }
- if (scanpos != vp) {
- struct oc_split_include *route =
- (struct oc_split_include *)malloc(
- sizeof(struct oc_split_include));
- route->route = strndup(vp, scanpos - vp);
- route->next = vpninfo->ip_info.split_includes;
- vpninfo->ip_info.split_includes = route;
- }
- vp = scanpos + 1;
- }
- } else {
- set_value = false;
- }
- if (set_value && vpninfo->dump_http_traffic) {
- char buf[1000];
- strcpy(buf, k);
- strcat(buf, ":");
- strcat(buf, v);
- dump_buf(vpninfo, '<', buf);
- }
- return 0;
- }
- static int h3c_authenticate(struct openconnect_info *vpninfo)
- {
- int ret;
- ret = openconnect_open_https(vpninfo);
- if (ret) {
- return ret;
- }
- assert(vpninfo->ssl_write);
- struct oc_text_buf *reqbuf = buf_alloc();
- buf_append(reqbuf, "GET /svpn/index.cgi HTTP/1.1\r\n");
- char *orig_ua = vpninfo->useragent;
- // The H3C client uses this UA on the first access
- vpninfo->useragent = (char *)"SSLVPN-Client/3.0";
- http_common_headers(vpninfo, reqbuf);
- vpninfo->useragent = orig_ua;
- buf_append(reqbuf, "\r\n");
- if (buf_error(reqbuf)) {
- vpn_progress(vpninfo, PRG_ERR, _("Error creating H3C connection request\n"));
- ret = buf_error(reqbuf);
- goto out;
- }
- if (vpninfo->dump_http_traffic) {
- dump_buf(vpninfo, '>', reqbuf->data);
- }
- ret = vpninfo->ssl_write(vpninfo, reqbuf->data, reqbuf->pos);
- if (ret < 0) {
- goto out;
- }
- struct oc_text_buf *resp_buf = buf_alloc();
- assert(resp_buf);
- if (buf_error(resp_buf)) {
- ret = buf_free(resp_buf);
- goto out;
- }
- ret = process_http_response(vpninfo, 1, NULL, resp_buf);
- /*
- if (vpninfo->dump_http_traffic) {
- dump_buf(vpninfo, '<', resp_buf->data);
- }
- */
- // buf_free(resp_buf);
- char *domain_list_str = NULL;
- if (ret == 302) {
- ret = handle_redirect(vpninfo);
- if (ret == 0) {
- buf_truncate(reqbuf);
- ret = do_https_request(vpninfo, "GET", NULL, reqbuf, &domain_list_str,
- NULL, HTTP_REDIRECT);
- if (ret > 0) {
- assert(domain_list_str != NULL);
- dump_buf(vpninfo, '<', domain_list_str);
- }
- }
- }
- assert(domain_list_str);
- xmlDocPtr xml_doc = xmlReadMemory(domain_list_str, strlen(domain_list_str),
- "noname.xml", NULL, XML_PARSE_NOERROR);
- assert(xml_doc);
- /*Get the root element node */
- xmlNode *root_element = xmlDocGetRootElement(xml_doc);
- // now we only use the first domain
- char *vpn_name = NULL, *vpn_url = NULL;
- // print_element_names(root_element);
- // <data> ... </data>
- if (xmlnode_is_named(root_element, "data")) {
- // find <domainlist> ... </domainlist>
- for (xmlNode *dom_list_node = root_element->children; dom_list_node;
- dom_list_node = dom_list_node->next) {
- if (xmlnode_is_named(dom_list_node, "domainlist")) {
- // find <domain> ... </domain>
- for (xmlNode *dom = dom_list_node->children; dom;
- dom = dom->next) {
- if (xmlnode_is_named(dom, "domain")) {
- for (xmlNode *dom_prop = dom->children;
- dom_prop; dom_prop = dom_prop->next) {
- if (xmlnode_is_named(dom_prop,
- "name")) {
- xmlnode_get_val(dom_prop,
- "name",
- &vpn_name);
- }
- if (xmlnode_is_named(dom_prop, "url")) {
- xmlnode_get_val(dom_prop, "url",
- &vpn_url);
- }
- if (vpn_name != NULL
- && vpn_url != NULL) {
- goto do_login;
- }
- }
- }
- }
- }
- }
- }
- do_login:
- assert(vpn_name != NULL && vpn_url != NULL);
- if (vpn_url[0] == '/') {
- vpninfo->urlpath = strdup(vpn_url + 1);
- } else {
- vpninfo->urlpath = strdup(vpn_url);
- }
- buf_truncate(reqbuf);
- char *gateway_info_str = NULL;
- ret = do_https_request(vpninfo, "GET", NULL, reqbuf, &gateway_info_str, NULL,
- HTTP_BODY_ON_ERROR);
- assert(ret >= 0 && gateway_info_str != NULL);
- dump_buf(vpninfo, '<', gateway_info_str);
- char *login_url = NULL;
- char *logout_url = NULL;
- char *checkonline_url = NULL;
- char *challenge_url = NULL;
- xmlDocPtr gwinfo_xml = xmlReadMemory(gateway_info_str, strlen(gateway_info_str),
- "noname.xml", NULL, XML_PARSE_NOERROR);
- assert(gwinfo_xml);
- xmlNode *gwinfo_root = xmlDocGetRootElement(gwinfo_xml);
- assert(gwinfo_root);
- // <data> ... </data>
- if (xmlnode_is_named(gwinfo_root, "data")) {
- // find <gatewayinfo> ... </gatewayinfo>
- for (xmlNode *gw_node = gwinfo_root->children; gw_node;
- gw_node = gw_node->next) {
- if (xmlnode_is_named(gw_node, "gatewayinfo")) {
- // we ignore <auth> ... </auth>, just find <url> ... </url>
- for (xmlNode *url_nodes = gw_node->children; url_nodes;
- url_nodes = url_nodes->next) {
- if (xmlnode_is_named(url_nodes, "url")) {
- for (xmlNode *method_url = url_nodes->children;
- method_url;
- method_url = method_url->next) {
- xmlnode_get_val(method_url, "login",
- &login_url);
- xmlnode_get_val(method_url, "logout",
- &logout_url);
- xmlnode_get_val(method_url,
- "checkonline",
- &checkonline_url);
- xmlnode_get_val(method_url, "challenge",
- &challenge_url);
- }
- if (login_url != NULL && logout_url != NULL
- && checkonline_url != NULL
- && challenge_url != NULL) {
- goto do_visit_login_url;
- }
- }
- }
- }
- }
- }
- do_visit_login_url:
- if (login_url[0] == '/') {
- add_option_dup(&vpninfo->cstp_options, "login_url", login_url + 1, -1);
- } else {
- add_option_dup(&vpninfo->cstp_options, "login_url", login_url, -1);
- }
- if (logout_url[0] == '/') {
- add_option_dup(&vpninfo->cstp_options, "logout_url", logout_url + 1, -1);
- } else {
- add_option_dup(&vpninfo->cstp_options, "logout_url", logout_url, -1);
- }
- if (checkonline_url[0] == '/') {
- add_option_dup(&vpninfo->cstp_options, "checkonline_url", checkonline_url + 1,
- -1);
- } else {
- add_option_dup(&vpninfo->cstp_options, "checkonline_url", checkonline_url, -1);
- }
- if (challenge_url[0] == '/') {
- add_option_dup(&vpninfo->cstp_options, "challenge_url", challenge_url + 1, -1);
- } else {
- add_option_dup(&vpninfo->cstp_options, "challenge_url", challenge_url, -1);
- }
- char username[1000], password[1000];
- assert(h3c_request_user_auth(vpninfo, username, password) == 0);
- assert(login_url != NULL && logout_url != NULL && checkonline_url != NULL
- && challenge_url != NULL);
- if (login_url[0] == '/') {
- vpninfo->urlpath = strdup(login_url + 1);
- } else {
- vpninfo->urlpath = strdup(login_url);
- }
- buf_truncate(reqbuf);
- struct oc_text_buf *request_data = buf_alloc();
- buf_append(request_data, "request=");
- buf_append_urlencoded(request_data, "<data><username>");
- buf_append_urlencoded(request_data, username);
- buf_append_urlencoded(request_data, "</username><password>");
- buf_append_urlencoded(request_data, password);
- buf_append_urlencoded(request_data, "</password></data>\r\n");
- char *login_resp = NULL;
- ret = do_https_request(vpninfo, "POST", "application/x-www-form-urlencoded",
- request_data, &login_resp, NULL, HTTP_BODY_ON_ERROR);
- if (ret >= 0 && login_resp != NULL) {
- dump_buf(vpninfo, '<', login_resp);
- }
- // get VPN parameter
- vpninfo->urlpath = (char *)"";
- buf_truncate(reqbuf);
- buf_truncate(request_data);
- char *handshake_resp = NULL;
- do_https_request(vpninfo, "NET_EXTEND", NULL, request_data, &handshake_resp,
- h3c_get_vpn_param_from_headers, HTTP_BODY_ON_ERROR);
- /* ignore the error */
- ret = 0;
- monitor_fd_new(vpninfo, ssl);
- monitor_read_fd(vpninfo, ssl);
- monitor_except_fd(vpninfo, ssl);
- vpninfo->ip_info.mtu = 1400;
- out:
- buf_free(reqbuf);
- return ret;
- }
- int h3c_connect(struct openconnect_info *vpninfo)
- {
- return h3c_authenticate(vpninfo);
- }
- int h3c_bye(struct openconnect_info *vpninfo, const char *reason)
- {
- openconnect_close_https(vpninfo, 0);
- char *logout_url = NULL;
- for (struct oc_vpn_option *opt = vpninfo->cstp_options; opt != NULL; opt = opt->next) {
- if (strcmp(opt->option, "logout_url") == 0) {
- logout_url = opt->value;
- }
- }
- if (logout_url != NULL) {
- char *logout_resp = NULL;
- vpninfo->urlpath = strdup(logout_url);
- int ret = do_https_request(vpninfo, "GET", NULL, NULL, &logout_resp, NULL,
- HTTP_BODY_ON_ERROR);
- return ret;
- }
- return -EINVAL;
- }
- static void h3c_handle_outgoing(struct openconnect_info *vpninfo)
- {
- vpninfo->ssl_times.last_tx = time(NULL);
- unmonitor_write_fd(vpninfo, ssl);
- vpn_progress(vpninfo, PRG_TRACE, _("Packet outgoing:\n"));
- store_le16(&vpninfo->current_ssl_pkt->h3c.type, 1);
- store_be16(&vpninfo->current_ssl_pkt->h3c.len, vpninfo->current_ssl_pkt->len);
- int ret = ssl_nonblock_write(vpninfo, 0, &vpninfo->current_ssl_pkt->h3c.type,
- vpninfo->current_ssl_pkt->len + 4);
- if (ret < 0) {
- vpn_progress(vpninfo, PRG_ERR, _("Send packet failed\n"));
- /* TODO: we don't know what to do now */
- }
- }
- int h3c_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
- {
- while (readable) {
- /* Some servers send us packets that are larger than
- negotiated MTU. We reserve some extra space to
- handle that */
- int receive_mtu = MAX(16384, vpninfo->deflate_pkt_size ?: vpninfo->ip_info.mtu);
- int len;
- if (!vpninfo->cstp_pkt) {
- vpninfo->partial_rec_size = 0;
- vpninfo->cstp_pkt = alloc_pkt(vpninfo, receive_mtu);
- if (!vpninfo->cstp_pkt) {
- vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
- break;
- }
- }
- len = ssl_nonblock_read(vpninfo, 0,
- vpninfo->cstp_pkt->data + vpninfo->partial_rec_size,
- receive_mtu - vpninfo->partial_rec_size);
- if (!len)
- break;
- if (len < 0) {
- /* goto do_reconnect; */
- return -1;
- }
- if (vpninfo->partial_rec_size) {
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Received %d more bytes after partial %d\n"), len,
- vpninfo->partial_rec_size);
- len += vpninfo->partial_rec_size;
- vpninfo->partial_rec_size = len;
- }
- vpninfo->ssl_times.last_rx = time(NULL);
- unsigned char *buf = vpninfo->cstp_pkt->data;
- if (buf[0] == 1) {
- uint16_t iplen = load_be16(buf + 2);
- if (len - 4 >= iplen) {
- if (len - 4 != iplen) {
- dump_buf_hex(vpninfo, PRG_DEBUG, '>', buf, len);
- }
- memmove(buf, buf + 4, len - 4);
- vpninfo->cstp_pkt->len = len - 4;
- vpninfo->partial_rec_size = 0;
- vpn_progress(vpninfo, PRG_TRACE,
- _("Moved down %d bytes after previous packet\n"),
- len);
- }
- } else {
- dump_buf_hex(vpninfo, PRG_ERR, '>', buf, len);
- }
- queue_packet(&vpninfo->incoming_queue, vpninfo->cstp_pkt);
- vpninfo->cstp_pkt = NULL;
- }
- if (vpninfo->current_ssl_pkt) {
- /* TODO: we don't know what to do yet */
- h3c_handle_outgoing(vpninfo);
- vpninfo->current_ssl_pkt = NULL;
- }
- while (vpninfo->current_ssl_pkt = dequeue_packet(&vpninfo->outgoing_queue)) {
- h3c_handle_outgoing(vpninfo);
- }
- return 1;
- }
- void h3c_http_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
- {
- char *orig_ua = vpninfo->useragent;
- vpninfo->useragent = (char *)"SSLVPN-Client/7.0";
- http_common_headers(vpninfo, buf);
- vpninfo->useragent = orig_ua;
- }
- int h3c_obtain_cookie(struct openconnect_info *vpninfo)
- {
- return 0;
- }
|