[ ok, sending plain text without wrapping long lines seems to be harder than it should be. sorry for the additional list submission] I've included a patch that provides better compatibility with CSD on ASA head ends. E.g. it allows to specify the version string that is presented to the ASA. Previous to this patch, OC presents it's own version e.g. 0.7.8 but that could cause rejection on the head end if it looks for a matching AC version string. It also contains a change that allows to even control more of the client system data that is passed in the XML during authentication. This part is entirely optional but might help to provide better compatibility going forward. The patch works for me... especially the version-string seems to be important in some environments to allow a successful connection, the XML changes using the mobile_info struct might be seen as experimental / optional. Thanks, -ralph Signed-off-by: Ralph Schmieder <ralph.schmieder at gmail.com> --- auth.c | 12 ++++++-- cstp.c | 2 +- env.sh | 60 ++++++++++++++++++++++++++++++++++++++ http.c | 10 +++++++ library.c | 5 +++- main.c | 65 +++++++++++++++++++++++++++++++++++++++++- openconnect-internal.h | 3 ++ openconnect.8.in | 23 +++++++++++++++ openconnect.h | 3 +- 9 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 env.sh diff --git a/auth.c b/auth.c index 5883988..1236f3f 100644 --- a/auth.c +++ b/auth.c @@ -705,7 +705,7 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char xmlNodePtr *rootp) { xmlDocPtr doc; - xmlNodePtr root, node; + xmlNodePtr root, node, child; doc = xmlNewDoc(XCAST("1.0")); if (!doc) @@ -720,7 +720,7 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char goto bad; xmlDocSetRootElement(doc, root); - node = xmlNewTextChild(root, NULL, XCAST("version"), XCAST(openconnect_version_str)); + node = xmlNewTextChild(root, NULL, XCAST("version"), XCAST(vpninfo->version_string)); if (!node) goto bad; if (!xmlNewProp(node, XCAST("who"), XCAST("vpn"))) @@ -734,6 +734,14 @@ static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char !xmlNewProp(node, XCAST("device-type"), XCAST(vpninfo->mobile_device_type)) || !xmlNewProp(node, XCAST("unique-id"), XCAST(vpninfo->mobile_device_uniqueid))) goto bad; + if (vpninfo->mobile_mac_address) { + node = xmlNewTextChild(root, NULL, XCAST("mac-address-list"), NULL); + if (!node) + goto bad; + child = xmlNewTextChild(node, NULL, XCAST("mac-address"), XCAST(vpninfo->mobile_mac_address)); + if (!child) + goto bad; + } } return doc; diff --git a/cstp.c b/cstp.c index 68c3d51..625c414 100644 --- a/cstp.c +++ b/cstp.c @@ -179,7 +179,7 @@ static void append_mobile_headers(struct openconnect_info *vpninfo, struct oc_te { if (vpninfo->mobile_platform_version) { buf_append(buf, "X-AnyConnect-Identifier-ClientVersion: %s\r\n", - openconnect_version_str); + vpninfo->version_string); buf_append(buf, "X-AnyConnect-Identifier-Platform: %s\r\n", vpninfo->platname); buf_append(buf, "X-AnyConnect-Identifier-PlatformVersion: %s\r\n", diff --git a/env.sh b/env.sh new file mode 100644 index 0000000..17faea0 --- /dev/null +++ b/env.sh @@ -0,0 +1,60 @@ +# +# This is entirely optional to provide better compatibility as +# this information may show in the ASA head-end output or can +# be used for CSD evaluation (e.g. if the wrong information is +# presented, the head-end may deny the connection). +# +# Source this script (provide password for 'sudo dmidecode') +# provide -E to sudo when launching openconnect to make sure +# the environment is preserved: +# +# The script obviously relies on some Linux tools to provide +# actual information. However, the variables could also be +# statically provided when pretending to be something else. +# +# Example: +# +# OC_DEVICE_TYPE="VMware, Inc. VMware Virtual Platform" +# OC_PLATFORM_VERSION="Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64" +# OC_UNIQUE_ID="A1C043E19F166410ABC2C2BDC8C376DA4F7F03513FBE1DCB3A066F22C65C12B6" +# OC_MAC_ADDRESS="00-0c-29-0c-0e-57" +# +# - source env.sh +# - sudo -E openconnect .... +# +#set -x + +# get system information from DMI +DMI=$(sudo dmidecode | grep -A8 'System Information') + +# extract stuff we're interested in +MANUFACTURER=$(echo "$DMI" | grep 'Manufacturer' | cut -d: -f2) +PRODUCT=$(echo "$DMI" | grep 'Product Name' | cut -d: -f2) +SERIAL=$(echo "$DMI" | grep 'Serial Number' | cut -d: -f2) +UUID=$(echo "$DMI" | grep 'UUID' | cut -d: -f2) + +# synthesize / acquire device information +OC_DEVICE_TYPE="${MANUFACTURER## } ${PRODUCT## }" +OC_PLATFORM_VERSION=$(uname -srvp) +OC_UNIQUE_ID=$(echo $UUID$SERIAL | openssl sha256 | sed 's/^.* //' | tr [[:lower:]] [[:upper:]]) + +# get the device that has the default route +GW_DEV=$(ip route | sed -nr '/^default/s/.* dev ([[:alnum:]]+) .*$/\1/p') + +# does it exist and is it different from tun? +# (if tunnel is running it potentially has the default route) +# if not, set it to all-zeroes +if [[ -n ${GW_DEV} && ! "${GW_DEV}" =~ ^tun[0-9]+$ ]]; then + OC_MAC_ADDRESS=$(ip link show dev ${GW_DEV} | \ + sed -nr '2s/^.*ether (([0-9a-f]{2}(:?)){6}) .*$/\1/p' | \ + tr ':' '-') +else + OC_MAC_ADDRESS="00-00-00-00-00-00" +fi + +# export the information used by OpenConnect +export OC_DEVICE_TYPE +export OC_PLATFORM_VERSION +export OC_UNIQUE_ID +export OC_MAC_ADDRESS + diff --git a/http.c b/http.c index 7ff3bf7..397141c 100644 --- a/http.c +++ b/http.c @@ -1001,6 +1001,16 @@ char *openconnect_create_useragent(const char *base) return uagent; } +char *openconnect_create_version_string(void) +{ + char *version; + + if (asprintf(&version, "%s", openconnect_version_str) < 0) + return NULL; + + return version; +} + static int proxy_gets(struct openconnect_info *vpninfo, char *buf, size_t len) { int i = 0; diff --git a/library.c b/library.c index 3d13499..b26d938 100644 --- a/library.c +++ b/library.c @@ -78,6 +78,7 @@ struct openconnect_info *openconnect_vpninfo_new(const char *useragent, vpninfo->max_qlen = 10; vpninfo->localname = strdup("localhost"); vpninfo->useragent = openconnect_create_useragent(useragent); + vpninfo->version_string = openconnect_create_version_string(); vpninfo->validate_peer_cert = validate_peer_cert; vpninfo->write_new_config = write_new_config; vpninfo->process_auth_form = process_auth_form; @@ -276,11 +277,13 @@ int openconnect_set_reported_os(struct openconnect_info *vpninfo, int openconnect_set_mobile_info(struct openconnect_info *vpninfo, const char *mobile_platform_version, const char *mobile_device_type, - const char *mobile_device_uniqueid) + const char *mobile_device_uniqueid, + const char *mobile_mac_address) { STRDUP(vpninfo->mobile_platform_version, mobile_platform_version); STRDUP(vpninfo->mobile_device_type, mobile_device_type); STRDUP(vpninfo->mobile_device_uniqueid, mobile_device_uniqueid); + STRDUP(vpninfo->mobile_mac_address, mobile_mac_address); return 0; } diff --git a/main.c b/main.c index 379cf5d..1a95d49 100644 --- a/main.c +++ b/main.c @@ -188,6 +188,7 @@ enum { OPT_LOCAL_HOSTNAME, OPT_PROTOCOL, OPT_PASSTOS, + OPT_VERSION, }; #ifdef __sun__ @@ -253,6 +254,7 @@ static const struct option long_options[] = { OPTION("resolve", 1, OPT_RESOLVE), OPTION("key-password-from-fsid", 0, OPT_KEY_PASSWORD_FROM_FSID), OPTION("useragent", 1, OPT_USERAGENT), + OPTION("version-string", 1, OPT_VERSION), OPTION("local-hostname", 1, OPT_LOCAL_HOSTNAME), OPTION("disable-ipv6", 0, OPT_DISABLE_IPV6), OPTION("no-proxy", 0, OPT_NO_PROXY), @@ -875,6 +877,8 @@ static void usage(void) printf(" --useragent=STRING %s\n", _("HTTP header User-Agent: field")); printf(" --local-hostname=STRING %s\n", _("Local hostname to advertise to server")); printf(" --os=STRING %s\n", _("OS type (linux,linux-64,win,...) to report")); + printf(" --version-string=STRING %s\n", _("reported version string during authentication")); + printf(" (%s %s)\n", _("default:"), openconnect_version_str); #ifndef _WIN32 printf("\n%s:\n", _("Trojan binary (CSD) execution")); @@ -1416,6 +1420,10 @@ int main(int argc, char **argv) free(vpninfo->useragent); vpninfo->useragent = dup_config_arg(); break; + case OPT_VERSION: + free(vpninfo->version_string); + vpninfo->version_string = dup_config_arg(); + break; case OPT_LOCAL_HOSTNAME: openconnect_set_localname(vpninfo, config_arg); break; @@ -1454,7 +1462,62 @@ int main(int argc, char **argv) openconnect_set_mobile_info(vpninfo, xstrdup("1.0"), dup_config_arg(), - xstrdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); + xstrdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), + NULL); + } + + /* This is ONLY applied for linux-64 and mac-intel, but could be used for + other platforms, too. + + Read env variables for device information provided during authentication + this includes: + + - device type (from 'dmidecode'), e.g. 'VMware, Inc. VMware Virtual Platform' + - platform version (from 'uname -srvp'), e.g. + 'Linux 3.19.0-61-generic #69~14.04.1-Ubuntu SMP Thu Jun 9 09:09:13 UTC 2016 x86_64' + - uuid (some 32 byte hex value) + - MAC address list (use first MAC address from interface which has default gateway) + + Sample output in <device-id> and <mac-address> elements: + (shortened and line-wrapped for readability) + + <?xml version="1.0" encoding="UTF-8"?> + <config-auth client="vpn" type="auth-reply"> + <version who="vpn">v7.07-lalala</version> + <device-id device-type="VMware, Inc. VMware Virtual Platform" + platform-version="Linux 3.19.0-61-generic #69~14.04.1-Ubuntu + SMP Thu Jun 9 09:09:13 UTC 2016 x86_64" + unique-id="AB92EE073BD690CA94A89E2...984701EEB1CC7C4C9666D5"> + linux-64 + </device-id> + <mac-address-list> + <mac-address>00-0c-29-1a-ee-36</mac-address> + </mac-address-list> + [...] + </config-auth> + + This is entirely optional to provide better compatibility as this information may show + in the ASA head-end output or can be used for CSD evaluation. + + sample script provided (env.sh), source script (provide password for 'sudo dmidecode') + provide -E to sudo to make sure environment is preserved + + - source env.sh + - sudo -E openconnect .... + */ + + if (!strcmp(config_arg, "linux-64") || !strcmp(os, "mac-intel")) { + vpn_progress(vpninfo, PRG_INFO, _("providing device info\n")); + vpn_progress(vpninfo, PRG_INFO, _("OC_PLATFORM_VERSION [%s]\n"), getenv("OC_PLATFORM_VERSION")); + vpn_progress(vpninfo, PRG_INFO, _("OC_DEVICE_TYPE [%s]\n"), getenv("OC_DEVICE_TYPE")); + vpn_progress(vpninfo, PRG_INFO, _("OC_UNIQUE_ID [%s]\n"), getenv("OC_UNIQUE_ID")); + vpn_progress(vpninfo, PRG_INFO, _("OC_MAC_ADDRESS [%s]\n"), getenv("OC_MAC_ADDRESS")); + + openconnect_set_mobile_info(vpninfo, + xstrdup(getenv("OC_PLATFORM_VERSION")), + xstrdup(getenv("OC_DEVICE_TYPE")), + xstrdup(getenv("OC_UNIQUE_ID")), + xstrdup(getenv("OC_MAC_ADDRESS"))); } break; case OPT_PASSTOS: diff --git a/openconnect-internal.h b/openconnect-internal.h index 729d301..766f76e 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -389,6 +389,7 @@ struct openconnect_info { char *mobile_platform_version; char *mobile_device_type; char *mobile_device_uniqueid; + char *mobile_mac_address; char *csd_token; char *csd_ticket; char *csd_stuburl; @@ -640,6 +641,7 @@ struct openconnect_info { int is_dyndns; /* Attempt to redo DNS lookup on each CSTP reconnect */ char *useragent; + char *version_string; /* userdefined openconnect_version_str */ const char *quit_reason; @@ -1044,6 +1046,7 @@ void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str); int buf_error(struct oc_text_buf *buf); int buf_free(struct oc_text_buf *buf); char *openconnect_create_useragent(const char *base); +char *openconnect_create_version_string(void); int process_proxy(struct openconnect_info *vpninfo, int ssl_sock); int internal_parse_url(const char *url, char **res_proto, char **res_host, int *res_port, char **res_path, int default_port); diff --git a/openconnect.8.in b/openconnect.8.in index 1951183..1b19495 100644 --- a/openconnect.8.in +++ b/openconnect.8.in @@ -64,6 +64,7 @@ openconnect \- Multi-protocol VPN client, for Cisco AnyConnect VPNs and others .OP \-\-resolve host:ip .OP \-\-servercert sha1 .OP \-\-useragent string +.OP \-\-version\-string string .OP \-\-local-hostname string .OP \-\-os string .B [https://]\fIserver\fB[:\fIport\fB][/\fIgroup\fB] @@ -504,6 +505,12 @@ Use as 'User\-Agent:' field value in HTTP header. (e.g. \-\-useragent 'Cisco AnyConnect VPN Agent for Windows 2.2.0133') .TP +.B \-\-version\-string=STRING +Use +.I STRING +as the software version reported to the head end. +(e.g. \-\-version\-string '2.2.0133') +.TP .B \-\-local-hostname=STRING Use .I STRING @@ -523,6 +530,22 @@ applied to the VPN session. If the gateway requires CSD, it will also cause the corresponding CSD trojan binary to be downloaded, so you may need to use .B \-\-csd\-wrapper if this code is not executable on the local machine. +.SH ENVIRONMENT +When the following environment variables are present, their information is +sent during authentication to the head-end. This is entirely optional and +may provide better compatibility when CSD is in use. +.TP +.B OC_DEVICE_TYPE +the device type of the client, e.g. "VMware, Inc. VMware Virtual Platform" +.TP +.B OC_PLATFORM_VERSION +the exact version of the client's operating system, e.g. "Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64" +.TP +.B OC_UNIQUE_ID +a unique 64 hex char long ID string representing the client, e.g. "A1C043E19F166410[...]DA4FCB3A066F22C65C12B6" +.TP +.B OC_MAC_ADDRESS +a MAC address identifying the client, e.g. "00-0c-29-0c-0e-57" .SH SIGNALS In the data phase of the connection, the following signals are handled: .TP diff --git a/openconnect.h b/openconnect.h index 74a5124..43a4984 100644 --- a/openconnect.h +++ b/openconnect.h @@ -509,7 +509,8 @@ int openconnect_set_reported_os(struct openconnect_info *, const char *os); int openconnect_set_mobile_info(struct openconnect_info *vpninfo, const char *mobile_platform_version, const char *mobile_device_type, - const char *mobile_device_uniqueid); + const char *mobile_device_uniqueid, + const char *mobile_mac_address); int openconnect_set_client_cert(struct openconnect_info *, const char *cert, const char *sslkey); const char *openconnect_get_ifname(struct openconnect_info *); -- 2.18.0