diff options
72 files changed, 4858 insertions, 93 deletions
diff --git a/hostapd/config_file.c b/hostapd/config_file.c index e1f8b20d..26e64fa8 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -366,6 +366,10 @@ static int hostapd_config_read_eap_user(const char *fname, EAP_TTLS_AUTH_MSCHAPV2; goto skip_eap; } + if (os_strcmp(start, "MACACL") == 0) { + user->macacl = 1; + goto skip_eap; + } wpa_printf(MSG_ERROR, "Unsupported EAP type " "'%s' on line %d in '%s'", start, line, fname); @@ -380,7 +384,7 @@ static int hostapd_config_read_eap_user(const char *fname, break; start = pos3; } - if (num_methods == 0 && user->ttls_auth == 0) { + if (num_methods == 0 && user->ttls_auth == 0 && !user->macacl) { wpa_printf(MSG_ERROR, "No EAP types configured on " "line %d in '%s'", line, fname); goto failed; @@ -1089,8 +1093,6 @@ static int hostapd_config_vht_capab(struct hostapd_config *conf, conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; if (os_strstr(capab, "[VHT160-80PLUS80]")) conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; - if (os_strstr(capab, "[VHT160-80PLUS80]")) - conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; if (os_strstr(capab, "[RXLDPC]")) conf->vht_capab |= VHT_CAP_RXLDPC; if (os_strstr(capab, "[SHORT-GI-80]")) @@ -1258,7 +1260,7 @@ static int parse_3gpp_cell_net(struct hostapd_bss_config *bss, char *buf, count = 1; for (pos = buf; *pos; pos++) { - if ((*pos < '0' && *pos > '9') && *pos != ';' && *pos != ',') + if ((*pos < '0' || *pos > '9') && *pos != ';' && *pos != ',') goto fail; if (*pos == ';') count++; @@ -1600,7 +1602,7 @@ static int hs20_parse_wan_metrics(struct hostapd_bss_config *bss, char *buf, fail: wpa_printf(MSG_ERROR, "Line %d: Invalid hs20_wan_metrics '%s'", - line, pos); + line, buf); os_free(wan_metrics); return -1; } diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 29d5d8b2..62652650 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -1090,8 +1090,8 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd) hapd->conf->num_deny_mac, sta->addr, &vlan_id) && (!vlan_id || vlan_id == sta->vlan_id)) - ap_sta_deauthenticate( - hapd, sta, + ap_sta_disconnect( + hapd, sta, sta->addr, WLAN_REASON_UNSPECIFIED); } } else if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED && @@ -1102,8 +1102,8 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd) hapd->conf->num_accept_mac, sta->addr, &vlan_id) || (vlan_id && vlan_id != sta->vlan_id)) - ap_sta_deauthenticate( - hapd, sta, + ap_sta_disconnect( + hapd, sta, sta->addr, WLAN_REASON_UNSPECIFIED); } } @@ -1281,6 +1281,63 @@ static int hostapd_ctrl_iface_mib(struct hostapd_data *hapd, char *reply, } +static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, + char *buf, size_t buflen) +{ + int ret; + char *pos; + u8 *data = NULL; + unsigned int vendor_id, subcmd; + struct wpabuf *reply; + size_t data_len = 0; + + /* cmd: <vendor id> <subcommand id> [<hex formatted data>] */ + vendor_id = strtoul(cmd, &pos, 16); + if (!isblank(*pos)) + return -EINVAL; + + subcmd = strtoul(pos, &pos, 10); + + if (*pos != '\0') { + if (!isblank(*pos++)) + return -EINVAL; + data_len = os_strlen(pos); + } + + if (data_len) { + data_len /= 2; + data = os_malloc(data_len); + if (!data) + return -ENOBUFS; + + if (hexstr2bin(pos, data, data_len)) { + wpa_printf(MSG_DEBUG, + "Vendor command: wrong parameter format"); + os_free(data); + return -EINVAL; + } + } + + reply = wpabuf_alloc((buflen - 1) / 2); + if (!reply) { + os_free(data); + return -ENOBUFS; + } + + ret = hostapd_drv_vendor_cmd(hapd, vendor_id, subcmd, data, data_len, + reply); + + if (ret == 0) + ret = wpa_snprintf_hex(buf, buflen, wpabuf_head_u8(reply), + wpabuf_len(reply)); + + wpabuf_free(reply); + os_free(data); + + return ret; +} + + static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, void *sock_ctx) { @@ -1486,6 +1543,10 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "CHAN_SWITCH ", 12) == 0) { if (hostapd_ctrl_iface_chan_switch(hapd, buf + 12)) reply_len = -1; + } else if (os_strncmp(buf, "VENDOR ", 7) == 0) { + reply_len = hostapd_ctrl_iface_vendor(hapd, buf + 7, reply, + reply_size); + } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index b5770a4f..5012fbc9 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1731,6 +1731,11 @@ own_ip_addr=127.0.0.1 # - is not the same as the MAC address of the radio # - is not the same as any other explicitly specified BSSID # +# Not all drivers support multiple BSSes. The exact mechanism for determining +# the driver capabilities is driver specific. With the current (i.e., a recent +# kernel) drivers using nl80211, this information can be checked with "iw list" +# (search for "valid interface combinations"). +# # Please note that hostapd uses some of the values configured for the first BSS # as the defaults for the following BSSes. However, it is recommended that all # BSSes include explicit configuration of all relevant configuration items. diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index 8caca4f6..c488b4fd 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -940,6 +940,27 @@ static int hostapd_cli_cmd_chan_switch(struct wpa_ctrl *ctrl, } +static int hostapd_cli_cmd_vendor(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + char cmd[256]; + int res; + + if (argc < 2 || argc > 3) { + printf("Invalid vendor command\n" + "usage: <vendor id> <command id> [<hex formatted command argument>]\n"); + return -1; + } + + res = os_snprintf(cmd, sizeof(cmd), "VENDOR %s %s %s", argv[0], argv[1], + argc == 3 ? argv[2] : ""); + if (res < 0 || (size_t) res >= sizeof(cmd) - 1) { + printf("Too long VENDOR command.\n"); + return -1; + } + return wpa_ctrl_command(ctrl, cmd); +} + + struct hostapd_cli_cmd { const char *cmd; int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]); @@ -988,6 +1009,7 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "chan_switch", hostapd_cli_cmd_chan_switch }, { "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif }, { "hs20_deauth_req", hostapd_cli_cmd_hs20_deauth_req }, + { "vendor", hostapd_cli_cmd_vendor }, { NULL, NULL } }; diff --git a/hs20/server/Makefile b/hs20/server/Makefile new file mode 100644 index 00000000..587633bb --- /dev/null +++ b/hs20/server/Makefile @@ -0,0 +1,45 @@ +all: hs20_spp_server + +ifndef CC +CC=gcc +endif + +ifndef LDO +LDO=$(CC) +endif + +ifndef CFLAGS +CFLAGS = -MMD -O2 -Wall -g +endif + +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/crypto + +LIBS += -lsqlite3 + +# Using glibc < 2.17 requires -lrt for clock_gettime() +LIBS += -lrt + +OBJS=spp_server.o +OBJS += hs20_spp_server.o +OBJS += ../../src/utils/xml-utils.o +OBJS += ../../src/utils/base64.o +OBJS += ../../src/utils/common.o +OBJS += ../../src/utils/os_unix.o +OBJS += ../../src/utils/wpa_debug.o +OBJS += ../../src/crypto/md5-internal.o +CFLAGS += $(shell xml2-config --cflags) +LIBS += $(shell xml2-config --libs) +OBJS += ../../src/utils/xml_libxml2.o + +hs20_spp_server: $(OBJS) + $(LDO) $(LDFLAGS) -o hs20_spp_server $(OBJS) $(LIBS) + +clean: + rm -f core *~ *.o *.d hs20_spp_server + rm -f ../../src/utils/*.o + rm -f ../../src/utils/*.d + rm -f ../../src/crypto/*.o + rm -f ../../src/crypto/*.d + +-include $(OBJS:%.o=%.d) diff --git a/hs20/server/ca/clean.sh b/hs20/server/ca/clean.sh new file mode 100644 index 00000000..c69a1f54 --- /dev/null +++ b/hs20/server/ca/clean.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +for i in server-client server server-revoked user ocsp; do + rm -f $i.csr $i.key $i.pem +done + +rm -f openssl.cnf.tmp +rm -r demoCA +rm -f ca.pem logo.asn1 logo.der server.der ocsp-server-cache.der +#rm -r rootCA diff --git a/hs20/server/ca/est-csrattrs.cnf b/hs20/server/ca/est-csrattrs.cnf new file mode 100644 index 00000000..b50ea00d --- /dev/null +++ b/hs20/server/ca/est-csrattrs.cnf @@ -0,0 +1,17 @@ +asn1 = SEQUENCE:attrs + +[attrs] +#oid1 = OID:challengePassword +attr1 = SEQUENCE:extreq +oid2 = OID:sha256WithRSAEncryption + +[extreq] +oid = OID:extensionRequest +vals = SET:extreqvals + +[extreqvals] + +oid1 = OID:macAddress +#oid2 = OID:imei +#oid3 = OID:meid +#oid4 = OID:DevId diff --git a/hs20/server/ca/est-csrattrs.sh b/hs20/server/ca/est-csrattrs.sh new file mode 100644 index 00000000..0b73a040 --- /dev/null +++ b/hs20/server/ca/est-csrattrs.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +openssl asn1parse -genconf est-csrattrs.cnf -out est-csrattrs.der -oid hs20.oid +base64 est-csrattrs.der > est-attrs.b64 diff --git a/hs20/server/ca/hs20.oid b/hs20/server/ca/hs20.oid new file mode 100644 index 00000000..a829ff29 --- /dev/null +++ b/hs20/server/ca/hs20.oid @@ -0,0 +1,7 @@ +1.3.6.1.1.1.1.22 macAddress +1.2.840.113549.1.9.14 extensionRequest +1.3.6.1.4.1.40808.1.1.1 id-wfa-hotspot-friendlyName +1.3.6.1.4.1.40808.1.1.2 id-kp-HS2.0Auth +1.3.6.1.4.1.40808.1.1.3 imei +1.3.6.1.4.1.40808.1.1.4 meid +1.3.6.1.4.1.40808.1.1.5 DevId diff --git a/hs20/server/ca/ocsp-req.sh b/hs20/server/ca/ocsp-req.sh new file mode 100644 index 00000000..931a2069 --- /dev/null +++ b/hs20/server/ca/ocsp-req.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +for i in *.pem; do + echo "===[ $i ]===================" + openssl ocsp -text -CAfile ca.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +# openssl ocsp -text -CAfile rootCA/cacert.pem -VAfile ca.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +done diff --git a/hs20/server/ca/ocsp-responder-ica.sh b/hs20/server/ca/ocsp-responder-ica.sh new file mode 100644 index 00000000..116c6e1c --- /dev/null +++ b/hs20/server/ca/ocsp-responder-ica.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner demoCA/cacert.pem -rkey demoCA/private/cakey-plain.pem -CA demoCA/cacert.pem -resp_no_certs -text diff --git a/hs20/server/ca/ocsp-responder.sh b/hs20/server/ca/ocsp-responder.sh new file mode 100644 index 00000000..8cebd745 --- /dev/null +++ b/hs20/server/ca/ocsp-responder.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner ocsp.pem -rkey ocsp.key -CA demoCA/cacert.pem -text diff --git a/hs20/server/ca/ocsp-update-cache.sh b/hs20/server/ca/ocsp-update-cache.sh new file mode 100644 index 00000000..8ddef9b9 --- /dev/null +++ b/hs20/server/ca/ocsp-update-cache.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +openssl ocsp \ + -no_nonce \ + -CAfile ca.pem \ + -verify_other demoCA/cacert.pem \ + -issuer demoCA/cacert.pem \ + -cert server.pem \ + -url http://localhost:8888/ \ + -respout ocsp-server-cache.der diff --git a/hs20/server/ca/openssl-root.cnf b/hs20/server/ca/openssl-root.cnf new file mode 100644 index 00000000..5b220fe8 --- /dev/null +++ b/hs20/server/ca/openssl-root.cnf @@ -0,0 +1,125 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Root CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./rootCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = whatever +output_password = whatever + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = WFA Hotspot 2.0 + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_req ] + +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName=DNS:example.com,DNS:another.example.com + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +basicConstraints = critical,CA:true +keyUsage = critical, cRLSign, keyCertSign + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning diff --git a/hs20/server/ca/openssl.cnf b/hs20/server/ca/openssl.cnf new file mode 100644 index 00000000..a939f081 --- /dev/null +++ b/hs20/server/ca/openssl.cnf @@ -0,0 +1,200 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Intermediate CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = ext_client # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +copy_extensions = copy + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = supplied +stateOrProvinceName = optional +organizationName = supplied +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_osu_server ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = supplied +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = whatever +output_password = whatever + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = FI +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = w1.fi + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, cRLSign, keyCertSign +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +# For SP intermediate CA +#subjectAltName=critical,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:engExample OSU +#nameConstraints=permitted;DNS:.w1.fi +#1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn + +[ v3_osu_server ] + +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, keyEncipherment +#@ALTNAME@ + +#logotypeoid=ASN1:SEQUENCE:LogotypeExtn +1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn +[LogotypeExtn] +communityLogos=EXP:0,SEQUENCE:LogotypeInfo +[LogotypeInfo] +# note: implicit tag converted to explicit for CHOICE +direct=EXP:0,SEQUENCE:LogotypeData +[LogotypeData] +image=SEQUENCE:LogotypeImage +[LogotypeImage] +imageDetails=SEQUENCE:LogotypeDetails +imageInfo=SEQUENCE:LogotypeImageInfo +[LogotypeDetails] +mediaType=IA5STRING:image/png +logotypeHash=SEQUENCE:HashAlgAndValues +logotypeURI=SEQUENCE:URI +[HashAlgAndValues] +value1=SEQUENCE:HashAlgAndValueSHA256 +#value2=SEQUENCE:HashAlgAndValueSHA1 +[HashAlgAndValueSHA256] +hashAlg=SEQUENCE:sha256_alg +hashValue=FORMAT:HEX,OCTETSTRING:4532f7ec36424381617c03c6ce87b55a51d6e7177ffafda243cebf280a68954d +[HashAlgAndValueSHA1] +hashAlg=SEQUENCE:sha1_alg +hashValue=FORMAT:HEX,OCTETSTRING:5e1d5085676eede6b02da14d31c523ec20ffba0b +[sha256_alg] +algorithm=OID:sha256 +[sha1_alg] +algorithm=OID:sha1 +[URI] +uri=IA5STRING:http://osu.w1.fi/w1fi_logo.png +[LogotypeImageInfo] +# default value color(1), component optional +#type=IMP:0,INTEGER:1 +fileSize=INTEGER:7549 +xSize=INTEGER:128 +ySize=INTEGER:80 +language=IMP:4,IA5STRING:zxx + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning + +[ ext_client ] + +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +#@ALTNAME@ +extendedKeyUsage = clientAuth + +[ ext_server ] + +# Hotspot 2.0 PKI requirements +basicConstraints=critical, CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +#@ALTNAME@ +extendedKeyUsage = critical, serverAuth +keyUsage = critical, keyEncipherment diff --git a/hs20/server/ca/setup.sh b/hs20/server/ca/setup.sh new file mode 100644 index 00000000..f61bf73b --- /dev/null +++ b/hs20/server/ca/setup.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +if [ -z "$OPENSSL" ]; then + OPENSSL=openssl +fi +export OPENSSL_CONF=$PWD/openssl.cnf +PASS=whatever + +fail() +{ + echo "$*" + exit 1 +} + +echo +echo "---[ Root CA ]----------------------------------------------------------" +echo + +cat openssl-root.cnf | sed "s/#@CN@/commonName_default = Hotspot 2.0 Trust Root CA - 99/" > openssl.cnf.tmp +mkdir -p rootCA/certs rootCA/crl rootCA/newcerts rootCA/private +touch rootCA/index.txt +if [ -e rootCA/private/cakey.pem ]; then + echo " * Use existing Root CA" +else + echo " * Generate Root CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:4096 -keyout rootCA/private/cakey.pem -out rootCA/careq.pem || fail "Failed to generate Root CA private key" + echo " * Sign Root CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out rootCA/cacert.pem -days 10957 -batch -keyfile rootCA/private/cakey.pem -passin pass:$PASS -selfsign -extensions v3_ca -outdir rootCA/newcerts -infiles rootCA/careq.pem || fail "Failed to sign Root CA certificate" +fi +if [ ! -e rootCA/crlnumber ]; then + echo 00 > rootCA/crlnumber +fi + +echo +echo "---[ Intermediate CA ]--------------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = w1.fi Hotspot 2.0 Intermediate CA/" > openssl.cnf.tmp +mkdir -p demoCA/certs demoCA/crl demoCA/newcerts demoCA/private +touch demoCA/index.txt +if [ -e demoCA/private/cakey.pem ]; then + echo " * Use existing Intermediate CA" +else + echo " * Generate Intermediate CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:2048 -keyout demoCA/private/cakey.pem -out demoCA/careq.pem || fail "Failed to generate Intermediate CA private key" + echo " * Sign Intermediate CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out demoCA/cacert.pem -days 3652 -batch -keyfile rootCA/private/cakey.pem -cert rootCA/cacert.pem -passin pass:$PASS -extensions v3_ca -infiles demoCA/careq.pem || fail "Failed to sign Intermediate CA certificate" + # horrible from security view point, but for testing purposes since OCSP responder does not seem to support -passin + openssl rsa -in demoCA/private/cakey.pem -out demoCA/private/cakey-plain.pem -passin pass:$PASS +fi +if [ ! -e demoCA/crlnumber ]; then + echo 00 > demoCA/crlnumber +fi + +echo +echo "OCSP responder" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = ocsp.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out ocsp.csr -keyout ocsp.key -extensions v3_OCSP +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -keyfile demoCA/private/cakey.pem -passin pass:$PASS -in ocsp.csr -out ocsp.pem -days 730 -extensions v3_OCSP + +echo +echo "---[ Server - to be revoked ] ------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = osu-revoked.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-revoked.csr -keyout server-revoked.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-revoked.csr -out server-revoked.pem -key $PASS -days 730 -extensions ext_server +$OPENSSL ca -revoke server-revoked.pem -key $PASS + +echo +echo "---[ Server - with client ext key use ] ---------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = osu-client.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-client.csr -keyout server-client.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-client.csr -out server-client.pem -key $PASS -days 730 -extensions ext_client + +echo +echo "---[ User ]-------------------------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = User/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out user.csr -keyout user.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in user.csr -out user.pem -key $PASS -days 730 -extensions ext_client + +echo +echo "---[ Server ]-----------------------------------------------------------" +echo + +ALT="DNS:osu.w1.fi" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:engw1.fi TESTING USE" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:finw1.fi TESTIKÄYTTÖ" + +cat openssl.cnf | + sed "s/#@CN@/commonName_default = osu.w1.fi/" | + sed "s/^##organizationalUnitName/organizationalUnitName/" | + sed "s/#@OU@/organizationalUnitName_default = Hotspot 2.0 Online Sign Up Server/" | + sed "s/#@ALTNAME@/subjectAltName=critical,$ALT/" \ + > openssl.cnf.tmp +echo $OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server || fail "Failed to generate server request" +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server.csr -out server.pem -key $PASS -days 730 -extensions ext_server -policy policy_osu_server || fail "Failed to sign server certificate" + +#dump logotype details for debugging +$OPENSSL x509 -in server.pem -out server.der -outform DER +openssl asn1parse -in server.der -inform DER | grep HEX | tail -1 | sed 's/.*://' | xxd -r -p > logo.der +openssl asn1parse -in logo.der -inform DER > logo.asn1 + + +echo +echo "---[ CRL ]---------------------------------------------------------------" +echo + +$OPENSSL ca -config $PWD/openssl.cnf -gencrl -md sha256 -out demoCA/crl/crl.pem -passin pass:$PASS + +echo +echo "---[ Verify ]------------------------------------------------------------" +echo + +$OPENSSL verify -CAfile rootCA/cacert.pem demoCA/cacert.pem +$OPENSSL verify -CAfile rootCA/cacert.pem -untrusted demoCA/cacert.pem *.pem + +cat rootCA/cacert.pem demoCA/cacert.pem > ca.pem diff --git a/hs20/server/ca/w1fi_logo.png b/hs20/server/ca/w1fi_logo.png Binary files differnew file mode 100644 index 00000000..ac7c259f --- /dev/null +++ b/hs20/server/ca/w1fi_logo.png diff --git a/hs20/server/hs20-osu-server.txt b/hs20/server/hs20-osu-server.txt new file mode 100644 index 00000000..80985f73 --- /dev/null +++ b/hs20/server/hs20-osu-server.txt @@ -0,0 +1,196 @@ +Hotspot 2.0 OSU server +====================== + +The information in this document is based on the assumption that Ubuntu +12.04 server (64-bit) distribution is used and the web server is +Apache2. Neither of these are requirements for the installation, but if +other combinations are used, the package names and configuration +parameters may need to be adjusted. + +NOTE: This implementation and the example configuration here is meant +only for testing purposes in a lab environment. This design is not +secure to be installed in a publicly available Internet server without +considerable amount of modification and review for security issues. + +NOTE: While this describes use on Ubuntu 12.04, the version of Apache2 +included in that distribution is not new enough to support all OSU +server validation steps. In other words, it may be most adapt the steps +described here to Ubuntu 13.10. + + +Build dependencies +------------------ + +Ubuntu 12.04 server +- default installation +- upgraded to latest package versions + sudo apt-get update + sudo apt-get upgrade + +Packages needed for running the service: + sudo apt-get install sqlite3 + sudo apt-get install apache2 + sudo apt-get install php5-sqlite libapache2-mod-php5 + +Additional packages needed for building the components: + sudo apt-get install build-essential + sudo apt-get install libsqlite3-dev + sudo apt-get install libssl-dev + sudo apt-get install libxml2-dev + + +Installation location +--------------------- + +Select a location for the installation root directory. The example here +assumes /home/user/hs20-server to be used, but this can be changed by +editing couple of files as indicated below. + +sudo mkdir -p /home/user/hs20-server +sudo chown $USER /home/user/hs20-server +mkdir -p /home/user/hs20-server/spp +mkdir -p /home/user/hs20-server/AS + + +Build +----- + +# hostapd as RADIUS server +cd hostapd + +#example build configuration +cat > .config <<EOF +CONFIG_DRIVER_NONE=y +CONFIG_PKCS12=y +CONFIG_RADIUS_SERVER=y +CONFIG_EAP=y +CONFIG_EAP_TLS=y +CONFIG_EAP_MSCHAPV2=y +CONFIG_EAP_PEAP=y +CONFIG_EAP_GTC=y +CONFIG_EAP_TTLS=y +CONFIG_EAP_SIM=y +CONFIG_EAP_AKA=y +CONFIG_EAP_AKA_PRIME=y +CONFIG_SQLITE=y +CONFIG_HS20=y +EOF + +make hostapd hlr_auc_gw +cp hostapd hlr_auc_gw /home/user/hs20-server/AS + +# build hs20_spp_server +cd ../hs20/server +make clean +make +cp hs20_spp_server /home/user/hs20-server/spp +# prepare database (web server user/group needs to have write access) +mkdir -p /home/user/hs20-server/AS/DB +sudo chgrp www-data /home/user/hs20-server/AS/DB +sudo chmod g+w /home/user/hs20-server/AS/DB +sqlite3 /home/user/hs20-server/AS/DB/eap_user.db < sql.txt +sudo chgrp www-data /home/user/hs20-server/AS/DB/eap_user.db +sudo chmod g+w /home/user/hs20-server/AS/DB/eap_user.db +# add example configuration (note: need to update URLs to match the system) +sqlite3 /home/user/hs20-server/AS/DB/eap_user.db < sql-example.txt + +# copy PHP scripts +# Modify config.php if different installation directory is used. +# Modify PHP scripts to get the desired behavior for user interaction (or use +# the examples as-is for initial testing). +cp -r www /home/user/hs20-server + + +# Configure subscription policies +mkdir -p /home/user/hs20-server/spp/policy +cat > /home/user/hs20-server/spp/policy/default.xml <<EOF +<Policy> + <PolicyUpdate> + <UpdateInterval>30</UpdateInterval> + <UpdateMethod>ClientInitiated</UpdateMethod> + <Restriction>Unrestricted</Restriction> + <URI>https://policy-server.osu.example.com/hs20/spp.php</URI> + </PolicyUpdate> +</Policy> +EOF + + +# Install Hotspot 2.0 SPP and OMA DM XML schema/DTD files + +# XML schema for SPP +# Copy the latest XML schema into /home/user/hs20-server/spp/spp.xsd + +# OMA DM Device Description Framework DTD +# Copy into /home/user/hs20-server/spp/dm_ddf-v1_2.dtd +# http://www.openmobilealliance.org/tech/DTD/dm_ddf-v1_2.dtd + + +# Configure RADIUS authentication service +# Note: Change the URL to match the setup +# Note: Install AAA server key/certificate and root CA in Key directory + +cat > /home/user/hs20-server/AS/as-sql.conf <<EOF +driver=none +radius_server_clients=as.radius_clients +eap_server=1 +eap_user_file=sqlite:DB/eap_user.db +ca_cert=Key/ca.pem +server_cert=Key/server.pem +private_key=Key/server.key +private_key_passwd=passphrase +eap_sim_db=unix:/tmp/hlr_auc_gw.sock db=eap_sim.db +subscr_remediation_url=https://subscription-server.osu.example.com/hs20/spp.php +EOF + +# Set RADIUS passphrase for the APs +# Note: Modify to match the setup +cat > /home/user/hs20-server/AS/as.radius_clients <<EOF +0.0.0.0/0 radius +EOF + + +Start RADIUS authentication server +---------------------------------- + +cd /home/user/hs20-server/AS +./hostapd -B as-sql.conf + + +Configure web server +-------------------- + +Edit /etc/apache2/sites-available/default-ssl + +Add following block just before "SSL Engine Switch" line": + + Alias /hs20/ "/home/user/hs20-server/www/" + <Directory "/home/user/hs20-server/www/"> + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + </Directory> + +Update SSL configuration to use the OSU server certificate/key. + +Enable default-ssl site and restart Apache2: + sudo a2ensite default-ssl + sudo a2enmod ssl + sudo service apache2 restart + + +Management UI +------------- + +The sample PHP scripts include a management UI for testing +purposes. That is available at https://<server>/hs20/users.php + + +AP configuration +---------------- + +APs can now be configured to use the OSU server as the RADIUS +authentication server. In addition, the OSU Provider List ANQP element +should be configured to use the SPP (SOAP+XML) option and with the +following Server URL: +https://<server>/hs20/spp.php/signup?realm=example.com diff --git a/hs20/server/hs20_spp_server.c b/hs20/server/hs20_spp_server.c new file mode 100644 index 00000000..591f66bb --- /dev/null +++ b/hs20/server/hs20_spp_server.c @@ -0,0 +1,187 @@ +/* + * Hotspot 2.0 SPP server - standalone version + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include <time.h> +#include <sqlite3.h> + +#include "common.h" +#include "xml-utils.h" +#include "spp_server.h" + + +static void write_timestamp(FILE *f) +{ + time_t t; + struct tm *tm; + + time(&t); + tm = localtime(&t); + + fprintf(f, "%04u-%02u-%02u %02u:%02u:%02u ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) +{ + va_list ap; + + if (ctx->debug_log == NULL) + return; + + write_timestamp(ctx->debug_log); + va_start(ap, fmt); + vfprintf(ctx->debug_log, fmt, ap); + va_end(ap); + + fprintf(ctx->debug_log, "\n"); +} + + +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node) +{ + char *str; + + if (ctx->debug_log == NULL) + return; + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + + write_timestamp(ctx->debug_log); + fprintf(ctx->debug_log, "%s: '%s'\n", title, str); + os_free(str); +} + + +static int process(struct hs20_svc *ctx) +{ + int dmacc = 0; + xml_node_t *soap, *spp, *resp; + char *user, *realm, *post, *str; + + ctx->addr = getenv("HS20ADDR"); + if (ctx->addr) + debug_print(ctx, 1, "Connection from %s", ctx->addr); + + user = getenv("HS20USER"); + if (user && strlen(user) == 0) + user = NULL; + realm = getenv("HS20REALM"); + if (realm == NULL) { + debug_print(ctx, 1, "HS20REALM not set"); + return -1; + } + post = getenv("HS20POST"); + if (post == NULL) { + debug_print(ctx, 1, "HS20POST not set"); + return -1; + } + + soap = xml_node_from_buf(ctx->xml, post); + if (soap == NULL) { + debug_print(ctx, 1, "Could not parse SOAP data"); + return -1; + } + debug_dump_node(ctx, "Received SOAP message", soap); + spp = soap_get_body(ctx->xml, soap); + if (spp == NULL) { + debug_print(ctx, 1, "Could not get SPP message"); + xml_node_free(ctx->xml, soap); + return -1; + } + debug_dump_node(ctx, "Received SPP message", spp); + + resp = hs20_spp_server_process(ctx, spp, user, realm, dmacc); + xml_node_free(ctx->xml, soap); + if (resp == NULL && user == NULL) { + debug_print(ctx, 1, "Request HTTP authentication"); + return 2; /* Request authentication */ + } + if (resp == NULL) { + debug_print(ctx, 1, "No response"); + return -1; + } + + soap = soap_build_envelope(ctx->xml, resp); + if (soap == NULL) { + debug_print(ctx, 1, "SOAP envelope building failed"); + return -1; + } + str = xml_node_to_str(ctx->xml, soap); + xml_node_free(ctx->xml, soap); + if (str == NULL) { + debug_print(ctx, 1, "Could not get node string"); + return -1; + } + printf("%s", str); + free(str); + + return 0; +} + + +static void usage(void) +{ + printf("usage:\n" + "hs20_spp_server -r<root directory> [-f<debug log>]\n"); +} + + +int main(int argc, char *argv[]) +{ + struct hs20_svc ctx; + int ret; + + os_memset(&ctx, 0, sizeof(ctx)); + for (;;) { + int c = getopt(argc, argv, "f:r:"); + if (c < 0) + break; + switch (c) { + case 'f': + if (ctx.debug_log) + break; + ctx.debug_log = fopen(optarg, "a"); + if (ctx.debug_log == NULL) { + printf("Could not write to %s\n", optarg); + return -1; + } + break; + case 'r': + ctx.root_dir = optarg; + break; + default: + usage(); + return -1; + } + } + if (ctx.root_dir == NULL) { + usage(); + return -1; + } + ctx.xml = xml_node_init_ctx(&ctx, NULL); + if (ctx.xml == NULL) + return -1; + if (hs20_spp_server_init(&ctx) < 0) { + xml_node_deinit_ctx(ctx.xml); + return -1; + } + + ret = process(&ctx); + debug_print(&ctx, 1, "process() --> %d", ret); + + xml_node_deinit_ctx(ctx.xml); + hs20_spp_server_deinit(&ctx); + if (ctx.debug_log) + fclose(ctx.debug_log); + + return ret; +} diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c new file mode 100644 index 00000000..4d77d0e8 --- /dev/null +++ b/hs20/server/spp_server.c @@ -0,0 +1,2263 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> +#include <sqlite3.h> + +#include "common.h" +#include "base64.h" +#include "md5_i.h" +#include "xml-utils.h" +#include "spp_server.h" + + +#define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp" + +#define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0" +#define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0" +#define URN_OMA_DM_DMACC "urn:oma:mo:oma-dm-dmacc:1.0" +#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" + + +/* TODO: timeout to expire sessions */ + +enum hs20_session_operation { + NO_OPERATION, + UPDATE_PASSWORD, + CONTINUE_SUBSCRIPTION_REMEDIATION, + CONTINUE_POLICY_UPDATE, + USER_REMEDIATION, + SUBSCRIPTION_REGISTRATION, + POLICY_REMEDIATION, + POLICY_UPDATE, + FREE_REMEDIATION, +}; + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field); +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field); +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc); + + +static int db_add_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *pw, + const char *redirect_uri, + enum hs20_session_operation operation) +{ + char *sql; + int ret = 0; + + sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm," + "operation,password,redirect_uri) " + "VALUES " + "(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q,%d,%Q,%Q)", + sessionid, user ? user : "", realm ? realm : "", + operation, pw ? pw : "", + redirect_uri ? redirect_uri : ""); + if (sql == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + ret = -1; + } + sqlite3_free(sql); + return ret; +} + + +static void db_update_session_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + const char *pw) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET password=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + pw, sessionid, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update session password: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_pps(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET pps=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + str, sessionid, user, realm); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session pps: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devinfo(struct hs20_svc *ctx, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devinfo=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devinfo: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devdetail(struct hs20_svc *ctx, + const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devdetail=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devdetail: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_remove_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid) +{ + char *sql; + + if (user == NULL || realm == NULL) { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "id=%Q", sessionid); + } else { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + user, realm, sessionid); + } + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to delete session entry from " + "sqlite database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + const char *dump) +{ + char *sql; + char *user_buf = NULL, *realm_buf = NULL; + + debug_print(ctx, 1, "eventlog: %s", notes); + + if (user == NULL) { + user_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "user"); + user = user_buf; + realm_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "realm"); + realm = realm_buf; + } + + sql = sqlite3_mprintf("INSERT INTO eventlog" + "(user,realm,sessionid,timestamp,notes,dump,addr)" + " VALUES (%Q,%Q,%Q," + "strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q)", + user, realm, sessionid, notes, + dump ? dump : "", ctx->addr ? ctx->addr : ""); + free(user_buf); + free(realm_buf); + if (sql == NULL) + return; + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add eventlog entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog_node(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + xml_node_t *node) +{ + char *str; + + if (node) + str = xml_node_to_str(ctx->xml, node); + else + str = NULL; + hs20_eventlog(ctx, user, realm, sessionid, notes, str); + free(str); +} + + +static void db_update_mo_str(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, + const char *str) +{ + char *sql; + if (user == NULL || realm == NULL || name == NULL) + return; + sql = sqlite3_mprintf("UPDATE users SET %s=%Q " + "WHERE identity=%Q AND realm=%Q AND phase2=1", + name, str, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update user MO entry in sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_update_mo(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, xml_node_t *mo) +{ + char *str; + + str = xml_node_to_str(ctx->xml, mo); + if (str == NULL) + return; + + db_update_mo_str(ctx, user, realm, name, str); + free(str); +} + + +static void add_text_node(struct hs20_svc *ctx, xml_node_t *parent, + const char *name, const char *value) +{ + xml_node_create_text(ctx->xml, parent, NULL, name, value ? value : ""); +} + + +static void add_text_node_conf(struct hs20_svc *ctx, const char *realm, + xml_node_t *parent, const char *name, + const char *field) +{ + char *val; + val = db_get_osu_config_val(ctx, realm, field); + xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : ""); + os_free(val); +} + + +static int new_password(char *buf, int buflen) +{ + int i; + + if (buflen < 1) + return -1; + buf[buflen - 1] = '\0'; + if (os_get_random((unsigned char *) buf, buflen - 1) < 0) + return -1; + + for (i = 0; i < buflen - 1; i++) { + unsigned char val = buf[i]; + val %= 2 * 26 + 10; + if (val < 26) + buf[i] = 'a' + val; + else if (val < 2 * 26) + buf[i] = 'A' + val - 26; + else + buf[i] = '0' + val - 2 * 26; + } + + return 0; +} + + +struct get_db_field_data { + const char *field; + char *value; +}; + + +static int get_db_field(void *ctx, int argc, char *argv[], char *col[]) +{ + struct get_db_field_data *data = ctx; + int i; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], data->field) == 0 && argv[i]) { + os_free(data->value); + data->value = os_strdup(argv[i]); + break; + } + } + + return 0; +} + + +static char * db_get_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, int dmacc) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT %s FROM users WHERE " + "%s=%Q AND realm=%Q AND phase2=1", + field, dmacc ? "osu_user" : "identity", + user, realm); + if (cmd == NULL) + return NULL; + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "Could not find user '%s'", user); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' dmacc=%d --> " + "value='%s'", user, realm, field, dmacc, data.value); + + return data.value; +} + + +static int db_update_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, + const char *val, int dmacc) +{ + char *cmd; + int ret; + + cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE " + "%s=%Q AND realm=%Q AND phase2=1", + field, val, dmacc ? "osu_user" : "identity", user, + realm); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, + "Failed to update user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + ret = -1; + } else { + debug_print(ctx, 1, + "DB: user='%s' realm='%s' field='%s' set to '%s'", + user, realm, field, val); + ret = 0; + } + sqlite3_free(cmd); + + return ret; +} + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + if (user == NULL || realm == NULL) { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "id=%Q", field, session_id); + } else { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + field, user, realm, session_id); + } + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find session %s: %s", + session_id, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static int update_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *pw, int dmacc) +{ + char *cmd; + + cmd = sqlite3_mprintf("UPDATE users SET password=%Q, " + "remediation='' " + "WHERE %s=%Q AND phase2=1", + pw, dmacc ? "osu_user" : "identity", + user); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update database for user '%s'", + user); + } + sqlite3_free(cmd); + + return 0; +} + + +static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "EAPMethod"); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "EAPType", "21"); + add_text_node(ctx, node, "InnerMethod", "MS-CHAP-V2"); + + return 0; +} + + +static xml_node_t * build_username_password(struct hs20_svc *ctx, + xml_node_t *parent, + const char *user, const char *pw) +{ + xml_node_t *node; + char *b64; + + node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword"); + if (node == NULL) + return NULL; + + add_text_node(ctx, node, "Username", user); + + b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL); + if (b64 == NULL) + return NULL; + add_text_node(ctx, node, "Password", b64); + free(b64); + + return node; +} + + +static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred, + const char *user, const char *pw) +{ + xml_node_t *node; + + node = build_username_password(ctx, cred, user, pw); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "MachineManaged", "TRUE"); + add_text_node(ctx, node, "SoftTokenApp", ""); + add_eap_ttls(ctx, node); + + return 0; +} + + +static void add_creation_date(struct hs20_svc *ctx, xml_node_t *cred) +{ + char str[30]; + time_t now; + struct tm tm; + + time(&now); + gmtime_r(&now, &tm); + snprintf(str, sizeof(str), "%04u-%02u-%02uT%02u:%02u:%02uZ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + xml_node_create_text(ctx->xml, cred, NULL, "CreationDate", str); +} + + +static xml_node_t * build_credential_pw(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw) +{ + xml_node_t *cred; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + if (add_username_password(ctx, cred, user, pw) < 0) { + xml_node_free(ctx->xml, cred); + return NULL; + } + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_credential(struct hs20_svc *ctx, + const char *user, const char *realm, + char *new_pw, size_t new_pw_len) +{ + if (new_password(new_pw, new_pw_len) < 0) + return NULL; + debug_print(ctx, 1, "Update password to '%s'", new_pw); + return build_credential_pw(ctx, user, realm, new_pw); +} + + +static xml_node_t * build_credential_cert(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *cert_fingerprint) +{ + xml_node_t *cred, *cert; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + cert = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate"); + add_text_node(ctx, cert, "CertificateType", "x509v3"); + add_text_node(ctx, cert, "CertSHA256Fingerprint", cert_fingerprint); + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_post_dev_data_response(struct hs20_svc *ctx, + xml_namespace_t **ret_ns, + const char *session_id, + const char *status, + const char *error_code) +{ + xml_node_t *spp_node = NULL; + xml_namespace_t *ns; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppPostDevDataResponse"); + if (spp_node == NULL) + return NULL; + if (ret_ns) + *ret_ns = ns; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); + + if (error_code) { + xml_node_t *node; + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + if (node) + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int add_update_node(struct hs20_svc *ctx, xml_node_t *spp_node, + xml_namespace_t *ns, const char *uri, + xml_node_t *upd_node) +{ + xml_node_t *node, *tnds; + char *str; + + tnds = mo_to_tnds(ctx->xml, upd_node, 0, NULL, NULL); + if (!tnds) + return -1; + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) + return -1; + node = xml_node_create_text(ctx->xml, spp_node, ns, "updateNode", str); + free(str); + + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", uri); + + return 0; +} + + +static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + int machine_rem, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *cred; + char buf[400]; + char new_pw[33]; + char *real_user = NULL; + char *status; + char *cert; + + if (dmacc) { + real_user = db_get_val(ctx, user, realm, "identity", dmacc); + if (real_user == NULL) { + debug_print(ctx, 1, "Could not find user identity for " + "dmacc user '%s'", user); + return NULL; + } + } + + cert = db_get_val(ctx, user, realm, "cert", dmacc); + if (cert && cert[0] == '\0') + cert = NULL; + if (cert) { + cred = build_credential_cert(ctx, real_user ? real_user : user, + realm, cert); + } else { + cred = build_credential(ctx, real_user ? real_user : user, + realm, new_pw, sizeof(new_pw)); + } + free(real_user); + if (!cred) { + debug_print(ctx, 1, "Could not build credential"); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) { + debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); + return NULL; + } + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + debug_print(ctx, 1, "Could not add update node"); + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + machine_rem ? "machine remediation" : + "user remediation", cred); + xml_node_free(ctx->xml, cred); + + if (cert) { + debug_print(ctx, 1, "Certificate credential - no need for DB " + "password update on success notification"); + } else { + debug_print(ctx, 1, "Request DB password update on success " + "notification"); + db_add_session(ctx, user, realm, session_id, new_pw, NULL, + UPDATE_PASSWORD); + } + + return spp_node; +} + + +static xml_node_t * machine_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, int dmacc) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 1, dmacc); +} + + +static xml_node_t * policy_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *policy; + char buf[400]; + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "requires policy remediation", NULL); + + db_add_session(ctx, user, realm, session_id, NULL, NULL, + POLICY_REMEDIATION); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update (sub rem)", policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * browser_remediation(struct hs20_svc *ctx, + const char *session_id, + const char *redirect_uri, + const char *uri) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + + if (redirect_uri == NULL) { + debug_print(ctx, 1, "Missing redirectURI attribute for user " + "remediation"); + return NULL; + } + debug_print(ctx, 1, "redirectURI %s", redirect_uri); + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * user_remediation(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires user remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + USER_REMEDIATION); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * free_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires free/public account remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "free_remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + FREE_REMEDIATION); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * no_sub_rem(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id) +{ + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "no subscription mediation available", NULL); + + status = "No update available at this time"; + return build_post_dev_data_response(ctx, NULL, session_id, status, + NULL); +} + + +static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, + int dmacc, + const char *redirect_uri) +{ + char *type, *identity; + xml_node_t *ret; + char *free_account; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for remediation", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account && strcmp(free_account, user) == 0) { + free(free_account); + return no_sub_rem(ctx, user, realm, session_id); + } + free(free_account); + + type = db_get_val(ctx, user, realm, "remediation", dmacc); + if (type && strcmp(type, "free") != 0) { + char *val; + int shared = 0; + val = db_get_val(ctx, user, realm, "shared", dmacc); + if (val) + shared = atoi(val); + free(val); + if (shared) { + free(type); + return no_sub_rem(ctx, user, realm, session_id); + } + } + if (type && strcmp(type, "user") == 0) + ret = user_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "free") == 0) + ret = free_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "policy") == 0) + ret = policy_remediation(ctx, user, realm, session_id, dmacc); + else + ret = machine_remediation(ctx, user, realm, session_id, dmacc); + free(type); + + return ret; +} + + +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc) +{ + char *policy_id; + char fname[200]; + xml_node_t *policy, *node; + + policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc); + if (policy_id == NULL || strlen(policy_id) == 0) { + free(policy_id); + policy_id = strdup("default"); + if (policy_id == NULL) + return NULL; + } + + snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml", + ctx->root_dir, policy_id); + free(policy_id); + debug_print(ctx, 1, "Use policy file %s", fname); + + policy = node_from_file(ctx->xml, fname); + if (policy == NULL) + return NULL; + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI"); + if (node) { + char *url; + url = db_get_osu_config_val(ctx, realm, "policy_url"); + if (url == NULL) { + xml_node_free(ctx->xml, policy); + return NULL; + } + xml_node_set_text(ctx->xml, node, url); + free(url); + } + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate"); + if (node && use_dmacc) { + char *pw; + pw = db_get_val(ctx, user, realm, "osu_password", use_dmacc); + if (pw == NULL || + build_username_password(ctx, node, user, pw) == NULL) { + debug_print(ctx, 1, "Failed to add Policy/PolicyUpdate/" + "UsernamePassword"); + free(pw); + xml_node_free(ctx->xml, policy); + return NULL; + } + free(pw); + } + + return policy; +} + + +static xml_node_t * hs20_policy_update(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *policy; + char buf[400]; + const char *status; + char *identity; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for policy update", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE); + + status = "Update complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, "policy update", + policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * spp_get_mo(struct hs20_svc *ctx, xml_node_t *node, + const char *urn, int *valid, char **ret_err) +{ + xml_node_t *child, *tnds, *mo; + const char *name; + char *mo_urn; + char *str; + char fname[200]; + + *valid = -1; + if (ret_err) + *ret_err = NULL; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (strcmp(name, "moContainer") != 0) + continue; + mo_urn = xml_node_get_attr_value_ns(ctx->xml, child, SPP_NS_URI, + "moURN"); + if (strcasecmp(urn, mo_urn) == 0) { + xml_node_get_attr_value_free(ctx->xml, mo_urn); + break; + } + xml_node_get_attr_value_free(ctx->xml, mo_urn); + } + + if (child == NULL) + return NULL; + + debug_print(ctx, 1, "moContainer text for %s", urn); + debug_dump_node(ctx, "moContainer", child); + + str = xml_node_get_text(ctx->xml, child); + debug_print(ctx, 1, "moContainer payload: '%s'", str); + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + debug_print(ctx, 1, "could not parse moContainer text"); + return NULL; + } + + snprintf(fname, sizeof(fname), "%s/spp/dm_ddf-v1_2.dtd", ctx->root_dir); + if (xml_validate_dtd(ctx->xml, tnds, fname, ret_err) == 0) + *valid = 1; + else if (ret_err && *ret_err && + os_strcmp(*ret_err, "No declaration for attribute xmlns of element MgmtTree\n") == 0) { + free(*ret_err); + debug_print(ctx, 1, "Ignore OMA-DM DDF DTD validation error for MgmtTree namespace declaration with xmlns attribute"); + *ret_err = NULL; + *valid = 1; + } else + *valid = 0; + + mo = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (mo == NULL) { + debug_print(ctx, 1, "invalid moContainer for %s", urn); + } + + return mo; +} + + +static xml_node_t * spp_exec_upload_mo(struct hs20_svc *ctx, + const char *session_id, const char *urn) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node, *exec_node; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + node = xml_node_create(ctx->xml, exec_node, ns, "uploadMO"); + xml_node_add_attr(ctx->xml, node, ns, "moURN", urn); + + return spp_node; +} + + +static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx, + const char *realm, + const char *session_id, + const char *redirect_uri) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + char uri[300], *val; + + if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri, + SUBSCRIPTION_REGISTRATION) < 0) + return NULL; + val = db_get_osu_config_val(ctx, realm, "signup_url"); + if (val == NULL) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * hs20_user_input_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 0, dmacc); +} + + +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT value FROM osu_config WHERE realm=%Q AND " + "field=%Q", realm, field); + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = "value"; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find osu_config %s: %s", + realm, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static xml_node_t * build_pps(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw, const char *cert, + int machine_managed) +{ + xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp; + xml_node_t *cred, *eap, *userpw; + + pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "PerProviderSubscription"); + if (pps == NULL) + return NULL; + + add_text_node(ctx, pps, "UpdateIdentifier", "1"); + + c = xml_node_create(ctx->xml, pps, NULL, "Credential1"); + + add_text_node(ctx, c, "CredentialPriority", "1"); + + aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot"); + aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1"); + add_text_node_conf(ctx, realm, aaa1, "CertURL", + "aaa_trust_root_cert_url"); + add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint", + "aaa_trust_root_cert_fingerprint"); + + upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate"); + add_text_node(ctx, upd, "UpdateInterval", "4294967295"); + add_text_node(ctx, upd, "UpdateMethod", "ClientInitiated"); + add_text_node(ctx, upd, "Restriction", "HomeSP"); + add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url"); + trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot"); + add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url"); + add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint", + "trust_root_cert_fingerprint"); + + homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP"); + add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name"); + add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn"); + + xml_node_create(ctx->xml, c, NULL, "SubscriptionParameters"); + + cred = xml_node_create(ctx->xml, c, NULL, "Credential"); + add_creation_date(ctx, cred); + if (cert) { + xml_node_t *dc; + dc = xml_node_create(ctx->xml, cred, NULL, + "DigitalCertificate"); + add_text_node(ctx, dc, "CertificateType", "x509v3"); + add_text_node(ctx, dc, "CertSHA256Fingerprint", cert); + } else { + userpw = build_username_password(ctx, cred, user, pw); + add_text_node(ctx, userpw, "MachineManaged", + machine_managed ? "TRUE" : "FALSE"); + eap = xml_node_create(ctx->xml, userpw, NULL, "EAPMethod"); + add_text_node(ctx, eap, "EAPType", "21"); + add_text_node(ctx, eap, "InnerMethod", "MS-CHAP-V2"); + } + add_text_node(ctx, cred, "Realm", realm); + + return pps; +} + + +static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, + const char *session_id, + const char *user, + const char *realm) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *enroll, *exec_node; + char *val; + char password[11]; + char *b64; + + if (new_password(password, sizeof(password)) < 0) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + enroll = xml_node_create(ctx->xml, exec_node, ns, "getCertificate"); + xml_node_add_attr(ctx->xml, enroll, NULL, "enrollmentProtocol", "EST"); + + val = db_get_osu_config_val(ctx, realm, "est_url"); + xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI", + val ? val : ""); + os_free(val); + xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user); + + b64 = (char *) base64_encode((unsigned char *) password, + strlen(password), NULL); + if (b64 == NULL) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + xml_node_create_text(ctx->xml, enroll, ns, "estPassword", b64); + free(b64); + + db_update_session_password(ctx, user, realm, session_id, password); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx, + const char *session_id, + int enrollment_done) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node = NULL; + xml_node_t *pps, *tnds; + char buf[400]; + char *str; + char *user, *realm, *pw, *type, *mm; + const char *status; + int cert = 0; + int machine_managed = 0; + char *fingerprint; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + free(user); + free(realm); + free(pw); + return NULL; + } + + mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed"); + if (mm && atoi(mm)) + machine_managed = 1; + free(mm); + + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + + if (cert && !enrollment_done) { + xml_node_t *ret; + hs20_eventlog(ctx, user, realm, session_id, + "request client certificate enrollment", NULL); + ret = spp_exec_get_certificate(ctx, session_id, user, realm); + free(user); + free(realm); + free(pw); + return ret; + } + + if (!cert && strlen(pw) == 0) { + machine_managed = 1; + free(pw); + pw = malloc(11); + if (pw == NULL || new_password(pw, 11) < 0) { + free(user); + free(realm); + free(pw); + return NULL; + } + } + + status = "Provisioning complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + pps = build_pps(ctx, user, realm, pw, + fingerprint ? fingerprint : NULL, machine_managed); + free(fingerprint); + if (!pps) { + xml_node_free(ctx->xml, spp_node); + free(user); + free(realm); + free(pw); + return NULL; + } + + debug_print(ctx, 1, "Request DB subscription registration on success " + "notification"); + db_add_session_pps(ctx, user, realm, session_id, pps); + + hs20_eventlog_node(ctx, user, realm, session_id, + "new subscription", pps); + free(user); + free(pw); + + tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL); + xml_node_free(ctx->xml, pps); + if (!tnds) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str); + free(str); + snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm); + free(realm); + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf); + xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_free_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *cred; + char buf[400]; + char *status; + char *free_account, *pw; + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account == NULL) + return NULL; + pw = db_get_val(ctx, free_account, realm, "password", 0); + if (pw == NULL) { + free(free_account); + return NULL; + } + + cred = build_credential_pw(ctx, free_account, realm, pw); + free(free_account); + free(pw); + if (!cred) { + xml_node_free(ctx->xml, cred); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "free/public remediation", cred); + xml_node_free(ctx->xml, cred); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == USER_REMEDIATION) { + return hs20_user_input_remediation(ctx, user, realm, dmacc, + session_id); + } + + if (oper == FREE_REMEDIATION) { + return hs20_user_input_free_remediation(ctx, user, realm, + session_id); + } + + if (oper == SUBSCRIPTION_REGISTRATION) { + return hs20_user_input_registration(ctx, session_id, 0); + } + + debug_print(ctx, 1, "User session %s not in state for user input " + "completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == SUBSCRIPTION_REGISTRATION) + return hs20_user_input_registration(ctx, session_id, 1); + + debug_print(ctx, 1, "User session %s not in state for certificate " + "enrollment completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + xml_node_t *spp_node, *node; + char *status; + xml_namespace_t *ns; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper != SUBSCRIPTION_REGISTRATION) { + debug_print(ctx, 1, "User session %s not in state for " + "enrollment failure", session_id); + return NULL; + } + + status = "Error occurred"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + "Credentials cannot be provisioned at this time"); + db_remove_session(ctx, user, realm, session_id); + + return spp_node; +} + + +static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + const char *req_reason; + char *redirect_uri = NULL; + char *req_reason_buf = NULL; + char str[200]; + xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL; + xml_node_t *mo; + char *version; + int valid; + char *supp, *pos; + char *err; + + version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppVersion"); + if (version == NULL || strstr(version, "1.0") == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "SPP version not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported sppVersion", ret); + xml_node_get_attr_value_free(ctx->xml, version); + return ret; + } + xml_node_get_attr_value_free(ctx->xml, version); + + mo = get_node(ctx->xml, node, "supportedMOList"); + if (mo == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No supportedMOList element", ret); + return ret; + } + supp = xml_node_get_text(ctx->xml, mo); + for (pos = supp; pos && *pos; pos++) + *pos = tolower(*pos); + if (supp == NULL || + strstr(supp, URN_OMA_DM_DEVINFO) == NULL || + strstr(supp, URN_OMA_DM_DEVDETAIL) == NULL || + strstr(supp, URN_HS20_PPS) == NULL) { + xml_node_get_text_free(ctx->xml, supp); + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "One or more mandatory MOs not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported MOs", ret); + return ret; + } + xml_node_get_text_free(ctx->xml, supp); + + req_reason_buf = xml_node_get_attr_value(ctx->xml, node, + "requestReason"); + if (req_reason_buf == NULL) { + debug_print(ctx, 1, "No requestReason attribute"); + return NULL; + } + req_reason = req_reason_buf; + + redirect_uri = xml_node_get_attr_value(ctx->xml, node, "redirectURI"); + + debug_print(ctx, 1, "requestReason: %s sessionID: %s redirectURI: %s", + req_reason, session_id, redirect_uri); + snprintf(str, sizeof(str), "sppPostDevData: requestReason=%s", + req_reason); + hs20_eventlog(ctx, user, realm, session_id, str, NULL); + + devinfo = spp_get_mo(ctx, node, URN_OMA_DM_DEVINFO, &valid, &err); + if (devinfo == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevInfo moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevInfo MO", devinfo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors in DevInfo MO", + err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + if (user) + db_update_mo(ctx, user, realm, "devinfo", devinfo); + + devdetail = spp_get_mo(ctx, node, URN_OMA_DM_DEVDETAIL, &valid, &err); + if (devdetail == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevDetail moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevDetail MO", devdetail); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in DevDetail MO", err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + if (user) + db_update_mo(ctx, user, realm, "devdetail", devdetail); + + if (user) + mo = spp_get_mo(ctx, node, URN_HS20_PPS, &valid, &err); + else { + mo = NULL; + err = NULL; + } + if (user && mo) { + hs20_eventlog_node(ctx, user, realm, session_id, + "Received PPS MO", mo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in PPS MO", err); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + os_free(err); + return build_post_dev_data_response( + ctx, NULL, session_id, + "Error occurred", "Other"); + } + db_update_mo(ctx, user, realm, "pps", mo); + db_update_val(ctx, user, realm, "fetch_pps", "0", dmacc); + xml_node_free(ctx->xml, mo); + } + os_free(err); + + if (user && !mo) { + char *fetch; + int fetch_pps; + + fetch = db_get_val(ctx, user, realm, "fetch_pps", dmacc); + fetch_pps = fetch ? atoi(fetch) : 0; + free(fetch); + + if (fetch_pps) { + enum hs20_session_operation oper; + if (strcasecmp(req_reason, "Subscription remediation") + == 0) + oper = CONTINUE_SUBSCRIPTION_REMEDIATION; + else if (strcasecmp(req_reason, "Policy update") == 0) + oper = CONTINUE_POLICY_UPDATE; + else + oper = NO_OPERATION; + if (db_add_session(ctx, user, realm, session_id, NULL, + NULL, oper) < 0) + goto out; + + ret = spp_exec_upload_mo(ctx, session_id, + URN_HS20_PPS); + hs20_eventlog_node(ctx, user, realm, session_id, + "request PPS MO upload", + ret); + goto out; + } + } + + if (user && strcasecmp(req_reason, "MO upload") == 0) { + char *val = db_get_session_val(ctx, user, realm, session_id, + "operation"); + enum hs20_session_operation oper; + if (!val) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + goto out; + } + oper = atoi(val); + free(val); + if (oper == CONTINUE_SUBSCRIPTION_REMEDIATION) + req_reason = "Subscription remediation"; + else if (oper == CONTINUE_POLICY_UPDATE) + req_reason = "Policy update"; + else { + debug_print(ctx, 1, + "No pending operation in session %s", + session_id); + goto out; + } + } + + if (strcasecmp(req_reason, "Subscription registration") == 0) { + ret = hs20_subscription_registration(ctx, realm, session_id, + redirect_uri); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription registration response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Subscription remediation") == 0) { + ret = hs20_subscription_remediation(ctx, user, realm, + session_id, dmacc, + redirect_uri); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription remediation response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Policy update") == 0) { + ret = hs20_policy_update(ctx, user, realm, session_id, dmacc); + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update response", + ret); + goto out; + } + + if (strcasecmp(req_reason, "User input completed") == 0) { + if (devinfo) + db_add_session_devinfo(ctx, session_id, devinfo); + if (devdetail) + db_add_session_devdetail(ctx, session_id, devdetail); + ret = hs20_user_input_complete(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "user input completed response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment completed") == 0) { + ret = hs20_cert_enroll_completed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment failed") == 0) { + ret = hs20_cert_enroll_failed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment failed response", + ret); + goto out; + } + + debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'", + req_reason, user); +out: + xml_node_get_attr_value_free(ctx->xml, req_reason_buf); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + if (devinfo) + xml_node_free(ctx->xml, devinfo); + if (devdetail) + xml_node_free(ctx->xml, devdetail); + return ret; +} + + +static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx, + const char *session_id, + const char *status, + const char *error_code) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppExchangeComplete"); + + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); + + if (error_code) { + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int add_subscription(struct hs20_svc *ctx, const char *session_id) +{ + char *user, *realm, *pw, *pw_mm, *pps, *str; + char *sql; + int ret = -1; + char *free_account; + int free_acc; + char *type; + int cert = 0; + char *cert_pem, *fingerprint; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + pw_mm = db_get_session_val(ctx, NULL, NULL, session_id, + "machine_managed"); + pps = db_get_session_val(ctx, NULL, NULL, session_id, "pps"); + cert_pem = db_get_session_val(ctx, NULL, NULL, session_id, "cert_pem"); + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + goto out; + } + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + free_acc = free_account && strcmp(free_account, user) == 0; + free(free_account); + + debug_print(ctx, 1, + "New subscription: user='%s' realm='%s' free_acc=%d", + user, realm, free_acc); + debug_print(ctx, 1, "New subscription: pps='%s'", pps); + + sql = sqlite3_mprintf("UPDATE eventlog SET user=%Q, realm=%Q WHERE " + "sessionid=%Q AND (user='' OR user IS NULL)", + user, realm, session_id); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update eventlog in " + "sqlite database: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); + } + + if (free_acc) { + hs20_eventlog(ctx, user, realm, session_id, + "completed shared free account registration", + NULL); + ret = 0; + goto out; + } + + sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2," + "methods,cert,cert_pem,machine_managed) VALUES " + "(%Q,%Q,1,%Q,%Q,%Q,%d)", + user, realm, cert ? "TLS" : "TTLS-MSCHAPV2", + fingerprint ? fingerprint : "", + cert_pem ? cert_pem : "", + pw_mm && atoi(pw_mm) ? 1 : 0); + if (sql == NULL) + goto out; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + sqlite3_free(sql); + goto out; + } + sqlite3_free(sql); + + if (cert) + ret = 0; + else + ret = update_password(ctx, user, realm, pw, 0); + if (ret < 0) { + sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND " + "realm=%Q AND phase2=1", + user, realm); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + sqlite3_exec(ctx->db, sql, NULL, NULL, NULL); + sqlite3_free(sql); + } + } + + if (pps) + db_update_mo_str(ctx, user, realm, "pps", pps); + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devinfo"); + if (str) { + db_update_mo_str(ctx, user, realm, "devinfo", str); + free(str); + } + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devdetail"); + if (str) { + db_update_mo_str(ctx, user, realm, "devdetail", str); + free(str); + } + + if (ret == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "completed subscription registration", NULL); + } + +out: + free(user); + free(realm); + free(pw); + free(pw_mm); + free(pps); + free(cert_pem); + free(fingerprint); + return ret; +} + + +static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + char *status; + xml_node_t *ret; + char *val; + enum hs20_session_operation oper; + + status = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppStatus"); + if (status == NULL) { + debug_print(ctx, 1, "No sppStatus attribute"); + return NULL; + } + + debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s sessionID: %s", + status, session_id); + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (!val) { + debug_print(ctx, 1, + "No session active for user: %s sessionID: %s", + user, session_id); + oper = NO_OPERATION; + } else + oper = atoi(val); + + if (strcasecmp(status, "OK") == 0) { + char *new_pw = NULL; + + xml_node_get_attr_value_free(ctx->xml, status); + + if (oper == USER_REMEDIATION) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, "password"); + if (new_pw == NULL || strlen(new_pw) == 0) { + free(new_pw); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "No password " + "had been assigned for " + "session", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + oper = UPDATE_PASSWORD; + } + if (oper == UPDATE_PASSWORD) { + if (!new_pw) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, + "password"); + if (!new_pw) { + db_remove_session(ctx, user, realm, + session_id); + return NULL; + } + } + debug_print(ctx, 1, "Update user '%s' password in DB", + user); + if (update_password(ctx, user, realm, new_pw, dmacc) < + 0) { + debug_print(ctx, 1, "Failed to update user " + "'%s' password in DB", user); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + hs20_eventlog(ctx, user, realm, + session_id, "Updated user password " + "in database", NULL); + } + if (oper == SUBSCRIPTION_REGISTRATION) { + if (add_subscription(ctx, session_id) < 0) { + debug_print(ctx, 1, "Failed to add " + "subscription into DB"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + } + if (oper == POLICY_REMEDIATION || oper == POLICY_UPDATE) { + char *val; + val = db_get_val(ctx, user, realm, "remediation", + dmacc); + if (val && strcmp(val, "policy") == 0) + db_update_val(ctx, user, realm, "remediation", + "", dmacc); + free(val); + } + ret = build_spp_exchange_complete( + ctx, session_id, + "Exchange complete, release TLS connection", NULL); + hs20_eventlog_node(ctx, user, realm, session_id, + "Exchange completed", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + + ret = build_spp_exchange_complete(ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, "Error occurred", ret); + db_remove_session(ctx, user, realm, session_id); + xml_node_get_attr_value_free(ctx->xml, status); + return ret; +} + + +#define SPP_SESSION_ID_LEN 16 + +static char * gen_spp_session_id(void) +{ + FILE *f; + int i; + char *session; + + session = os_malloc(SPP_SESSION_ID_LEN * 2 + 1); + if (session == NULL) + return NULL; + + f = fopen("/dev/urandom", "r"); + if (f == NULL) { + os_free(session); + return NULL; + } + for (i = 0; i < SPP_SESSION_ID_LEN; i++) + os_snprintf(session + i * 2, 3, "%02x", fgetc(f)); + + fclose(f); + return session; +} + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc) +{ + xml_node_t *ret = NULL; + char *session_id; + const char *op_name; + char *xml_err; + char fname[200]; + + debug_dump_node(ctx, "received request", node); + + if (!dmacc && auth_user && auth_realm) { + char *real; + real = db_get_val(ctx, auth_user, auth_realm, "identity", 0); + if (!real) { + real = db_get_val(ctx, auth_user, auth_realm, + "identity", 1); + if (real) + dmacc = 1; + } + os_free(real); + } + + snprintf(fname, sizeof(fname), "%s/spp/spp.xsd", ctx->root_dir); + if (xml_validate(ctx->xml, node, fname, &xml_err) < 0) { + /* + * We may not be able to extract the sessionID from invalid + * input, but well, we can try. + */ + session_id = xml_node_get_attr_value_ns(ctx->xml, node, + SPP_NS_URI, + "sessionID"); + debug_print(ctx, 1, "SPP message failed validation"); + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "SPP message failed validation", node); + hs20_eventlog(ctx, auth_user, auth_realm, session_id, + "Validation errors", xml_err); + os_free(xml_err); + xml_node_get_attr_value_free(ctx->xml, session_id); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppValidationError"); + return ret; + } + + session_id = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sessionID"); + if (session_id) { + char *tmp; + debug_print(ctx, 1, "Received sessionID %s", session_id); + tmp = os_strdup(session_id); + xml_node_get_attr_value_free(ctx->xml, session_id); + if (tmp == NULL) + return NULL; + session_id = tmp; + } else { + session_id = gen_spp_session_id(); + if (session_id == NULL) { + debug_print(ctx, 1, "Failed to generate sessionID"); + return NULL; + } + debug_print(ctx, 1, "Generated sessionID %s", session_id); + } + + op_name = xml_node_get_localname(ctx->xml, node); + if (op_name == NULL) { + debug_print(ctx, 1, "Could not get op_name"); + return NULL; + } + + if (strcmp(op_name, "sppPostDevData") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppPostDevData received and validated", + node); + ret = hs20_spp_post_dev_data(ctx, node, auth_user, auth_realm, + session_id, dmacc); + } else if (strcmp(op_name, "sppUpdateResponse") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppUpdateResponse received and validated", + node); + ret = hs20_spp_update_response(ctx, node, auth_user, + auth_realm, session_id, dmacc); + } else { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "Unsupported SPP message received and " + "validated", node); + debug_print(ctx, 1, "Unsupported operation '%s'", op_name); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppUnknownCommandError"); + } + os_free(session_id); + + if (ret == NULL) { + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppInternalError"); + } + + return ret; +} + + +int hs20_spp_server_init(struct hs20_svc *ctx) +{ + char fname[200]; + ctx->db = NULL; + snprintf(fname, sizeof(fname), "%s/AS/DB/eap_user.db", ctx->root_dir); + if (sqlite3_open(fname, &ctx->db)) { + printf("Failed to open sqlite database: %s\n", + sqlite3_errmsg(ctx->db)); + sqlite3_close(ctx->db); + return -1; + } + + return 0; +} + + +void hs20_spp_server_deinit(struct hs20_svc *ctx) +{ + sqlite3_close(ctx->db); + ctx->db = NULL; +} diff --git a/hs20/server/spp_server.h b/hs20/server/spp_server.h new file mode 100644 index 00000000..7b27be3c --- /dev/null +++ b/hs20/server/spp_server.h @@ -0,0 +1,32 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef SPP_SERVER_H +#define SPP_SERVER_H + +struct hs20_svc { + const void *ctx; + struct xml_node_ctx *xml; + char *root_dir; + FILE *debug_log; + sqlite3 *db; + const char *addr; +}; + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node); + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc); +int hs20_spp_server_init(struct hs20_svc *ctx); +void hs20_spp_server_deinit(struct hs20_svc *ctx); + +#endif /* SPP_SERVER_H */ diff --git a/hs20/server/sql-example.txt b/hs20/server/sql-example.txt new file mode 100644 index 00000000..a25e2cdc --- /dev/null +++ b/hs20/server/sql-example.txt @@ -0,0 +1,17 @@ +INSERT INTO osu_config(realm,field,value) VALUES('example.com','fqdn','example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','friendly_name','Example Operator'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','spp_http_auth_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/spp-root-ca.der'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/aaa-root-ca.pem'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_account','free'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','policy_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','remediation_url','https://subscription-server.osu.example.com/hs20/remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_remediation_url','https://subscription-server.osu.example.com/hs20/free-remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','signup_url','https://subscription-server.osu.example.com/hs20/signup.php?session_id='); + + +INSERT INTO users(identity,realm,methods,password,phase2,shared) VALUES('free','example.com','TTLS-MSCHAPV2','free',1,1); + +INSERT INTO wildcards(identity,methods) VALUES('','TTLS,TLS'); diff --git a/hs20/server/sql.txt b/hs20/server/sql.txt new file mode 100644 index 00000000..66095382 --- /dev/null +++ b/hs20/server/sql.txt @@ -0,0 +1,59 @@ +CREATE TABLE eventlog( + user TEXT, + realm TEXT, + sessionid TEXT COLLATE NOCASE, + timestamp TEXT, + notes TEXT, + dump TEXT, + addr TEXT +); + +CREATE TABLE sessions( + timestamp TEXT, + id TEXT COLLATE NOCASE, + user TEXT, + realm TEXT, + password TEXT, + machine_managed BOOLEAN, + operation INTEGER, + type TEXT, + pps TEXT, + redirect_uri TEXT, + devinfo TEXT, + devdetail TEXT, + cert TEXT, + cert_pem TEXT +); + +CREATE index sessions_id_index ON sessions(id); + +CREATE TABLE osu_config( + realm TEXT, + field TEXT, + value TEXT +); + +CREATE TABLE users( + identity TEXT PRIMARY KEY, + methods TEXT, + password TEXT, + machine_managed BOOLEAN, + remediation TEXT, + phase2 INTEGER, + realm TEXT, + policy TEXT, + devinfo TEXT, + devdetail TEXT, + pps TEXT, + fetch_pps INTEGER, + osu_user TEXT, + osu_password TEXT, + shared INTEGER, + cert TEXT, + cert_pem TEXT +); + +CREATE TABLE wildcards( + identity TEXT PRIMARY KEY, + methods TEXT +); diff --git a/hs20/server/www/add-free.php b/hs20/server/www/add-free.php new file mode 100644 index 00000000..1efc6556 --- /dev/null +++ b/hs20/server/www/add-free.php @@ -0,0 +1,50 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_POST["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_POST["id"]); +else + die("Missing session id"); +if (strlen($id) < 32) + die("Invalid session id"); + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if (!$row || strlen($row['value']) == 0) { + die("Free account disabled"); +} + +$user = $row['value']; + +$row = $db->query("SELECT password FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if (!$row) + die("Free account not found"); + +$pw = $row['password']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', machine_managed='1' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/add-mo.php b/hs20/server/www/add-mo.php new file mode 100644 index 00000000..a3b45135 --- /dev/null +++ b/hs20/server/www/add-mo.php @@ -0,0 +1,56 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_POST["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_POST["id"]); +else + die("Missing session id"); + +$user = $_POST["user"]; +$pw = $_POST["password"]; +if (strlen($id) < 32 || !isset($user) || !isset($pw)) { + die("Invalid POST data"); +} + +if (strlen($user) < 1 || strncasecmp($user, "cert-", 5) == 0) { + echo "<html><body><p><red>Invalid username</red></p>\n"; + echo "<a href=\"signup.php?session_id=$id\">Try again</a>\n"; + echo "</body></html>\n"; + exit; +} + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$realm = $row['realm']; + +$userrow = $db->query("SELECT identity FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if ($userrow) { + echo "<html><body><p><red>Selected username is not available</red></p>\n"; + echo "<a href=\"signup.php?session_id=$id\">Try again</a>\n"; + echo "</body></html>\n"; + exit; +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', type='password' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/cert-enroll.php b/hs20/server/www/cert-enroll.php new file mode 100644 index 00000000..f023ca5a --- /dev/null +++ b/hs20/server/www/cert-enroll.php @@ -0,0 +1,39 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_GET["id"]); +else + die("Missing session id"); +if (strlen($id) < 32) + die("Invalid session id"); + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$user = sha1(mt_rand()); + +if (!$db->exec("UPDATE sessions SET user='$user', type='cert' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for client certificate enrollment')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/config.php b/hs20/server/www/config.php new file mode 100644 index 00000000..e3af4350 --- /dev/null +++ b/hs20/server/www/config.php @@ -0,0 +1,4 @@ +<?php +$osu_root = "/home/user/hs20-server"; +$osu_db = "sqlite:$osu_root/AS/DB/eap_user.db"; +?> diff --git a/hs20/server/www/est.php b/hs20/server/www/est.php new file mode 100644 index 00000000..a45648b9 --- /dev/null +++ b/hs20/server/www/est.php @@ -0,0 +1,198 @@ +<?php + +require('config.php'); + +$params = split("/", $_SERVER["PATH_INFO"], 3); +$realm = $params[1]; +$cmd = $params[2]; +$method = $_SERVER["REQUEST_METHOD"]; + +unset($user); +unset($rowid); + +if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { + $needed = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("EST: Missing auth parameter"); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("EST: Empty username"); + die('Authentication failed'); + } + + $db = new PDO($osu_db); + if (!$db) { + error_log("EST: Could not access database"); + die("Could not access database"); + } + + $sql = "SELECT rowid,password,operation FROM sessions " . + "WHERE user='$user' AND realm='$realm'"; + $q = $db->query($sql); + if (!$q) { + error_log("EST: Session not found for user=$user realm=$realm"); + die("Session not found"); + } + $row = $q->fetch(); + if (!$row) { + error_log("EST: Session fetch failed for user=$user realm=$realm"); + die('Session not found'); + } + $rowid = $row['rowid']; + + $oper = $row['operation']; + if ($oper != '5') { + error_log("EST: Unexpected operation $oper for user=$user realm=$realm"); + die("Session not found"); + } + $pw = $row['password']; + if (strlen($pw) < 1) { + error_log("EST: Empty password for user=$user realm=$realm"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($method . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("EST: Incorrect authentication response for user=$user realm=$realm"); + die('Authentication failed'); + } +} + + +if ($method == "GET" && $cmd == "cacerts") { + $fname = "$osu_root/est/$realm-cacerts.pkcs7"; + if (!file_exists($fname)) { + error_log("EST: cacerts - unknown realm $realm"); + die("Unknown realm"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($fname); + echo wordwrap(base64_encode($data), 72, "\n", true); + echo "\n"; + error_log("EST: cacerts"); +} else if ($method == "GET" && $cmd == "csrattrs") { + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/csrattrs"); + readfile("$osu_root/est/est-attrs.b64"); + error_log("EST: csrattrs"); +} else if ($method == "POST" && $cmd == "simpleenroll") { + if (!isset($user) || strlen($user) == 0) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("EST: simpleenroll - require authentication"); + die('Authentication required'); + } + if (!isset($_SERVER["CONTENT_TYPE"])) { + error_log("EST: simpleenroll without Content-Type"); + die("Missing Content-Type"); + } + if (!stristr($_SERVER["CONTENT_TYPE"], "application/pkcs10")) { + error_log("EST: simpleenroll - unexpected Content-Type: " . + $_SERVER["CONTENT_TYPE"]); + die("Unexpected Content-Type"); + } + + $data = file_get_contents("php://input"); + error_log("EST: simpleenroll - POST data from php://input: " . $data); + $req = base64_decode($data); + if ($req == FALSE) { + error_log("EST: simpleenroll - Invalid base64-encoded PKCS#10 data"); + die("Invalid base64-encoded PKCS#10 data"); + } + $cadir = "$osu_root/est"; + $reqfile = "$cadir/tmp/cert-req.pkcs10"; + $f = fopen($reqfile, "wb"); + fwrite($f, $req); + fclose($f); + + $req_pem = "$reqfile.pem"; + if (file_exists($req_pem)) + unlink($req_pem); + exec("openssl req -in $reqfile -inform DER -out $req_pem -outform PEM"); + if (!file_exists($req_pem)) { + error_log("EST: simpleenroll - Failed to parse certificate request"); + die("Failed to parse certificate request"); + } + + /* FIX: validate request and add HS 2.0 extensions to cert */ + $cert_pem = "$cadir/tmp/req-signed.pem"; + if (file_exists($cert_pem)) + unlink($cert_pem); + exec("openssl x509 -req -in $req_pem -CAkey $cadir/cakey.pem -out $cert_pem -CA $cadir/cacert.pem -CAserial $cadir/serial -days 365 -text"); + if (!file_exists($cert_pem)) { + error_log("EST: simpleenroll - Failed to sign certificate"); + die("Failed to sign certificate"); + } + + $cert = file_get_contents($cert_pem); + $handle = popen("openssl x509 -in $cert_pem -serial -noout", "r"); + $serial = fread($handle, 200); + pclose($handle); + $pattern = "/serial=(?P<snhex>[0-9a-fA-F:]*)/m"; + preg_match($pattern, $serial, $matches); + if (!isset($matches['snhex']) || strlen($matches['snhex']) < 1) { + error_log("EST: simpleenroll - Could not get serial number"); + die("Could not get serial number"); + } + $sn = str_replace(":", "", strtoupper($matches['snhex'])); + + $user = "cert-$sn"; + error_log("EST: user = $user"); + + $cert_der = "$cadir/tmp/req-signed.der"; + if (file_exists($cert_der)) + unlink($cert_der); + exec("openssl x509 -in $cert_pem -inform PEM -out $cert_der -outform DER"); + if (!file_exists($cert_der)) { + error_log("EST: simpleenroll - Failed to convert certificate"); + die("Failed to convert certificate"); + } + $der = file_get_contents($cert_der); + $fingerprint = hash("sha256", $der); + + $pkcs7 = "$cadir/tmp/est-client.pkcs7"; + if (file_exists($pkcs7)) + unlink($pkcs7); + exec("openssl crl2pkcs7 -nocrl -certfile $cert_pem -out $pkcs7 -outform DER"); + if (!file_exists($pkcs7)) { + error_log("EST: simpleenroll - Failed to prepare PKCS#7 file"); + die("Failed to prepare PKCS#7 file"); + } + + if (!$db->exec("UPDATE sessions SET user='$user', cert='$fingerprint', cert_pem='$cert' WHERE rowid=$rowid")) { + error_log("EST: simpleenroll - Failed to update session database"); + die("Failed to update session database"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($pkcs7); + $resp = wordwrap(base64_encode($data), 72, "\n", true); + echo $resp . "\n"; + error_log("EST: simpleenroll - PKCS#7 response: " . $resp); +} else { + header("HTTP/1.0 404 Not Found"); + error_log("EST: Unexpected method or path"); + die("Unexpected method or path"); +} + +?> diff --git a/hs20/server/www/free-remediation.php b/hs20/server/www/free-remediation.php new file mode 100644 index 00000000..5648b30e --- /dev/null +++ b/hs20/server/www/free-remediation.php @@ -0,0 +1,19 @@ +<html> +<head> +<title>Hotspot 2.0 - public and free hotspot - remediation</title> +</head> +<body> + +<h3>Hotspot 2.0 - public and free hotspot</h3> + +<p>Terms and conditions have changed. You need to accept the new terms +to continue using this network.</p> + +<p>Terms and conditions..</p> + +<?php +echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Accept</a><br>\n"; +?> + +</body> +</html> diff --git a/hs20/server/www/free.php b/hs20/server/www/free.php new file mode 100644 index 00000000..8195069e --- /dev/null +++ b/hs20/server/www/free.php @@ -0,0 +1,23 @@ +<html> +<head> +<title>Hotspot 2.0 - public and free hotspot</title> +</head> +<body> + +<?php + +$id = $_GET["session_id"]; + +echo "<h3>Hotspot 2.0 - public and free hotspot</h3>\n"; + +echo "<form action=\"add-free.php\" method=\"POST\">\n"; +echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n"; + +?> + +<p>Terms and conditions..</p> +<input type="submit" value="Accept"> +</form> + +</body> +</html> diff --git a/hs20/server/www/redirect.php b/hs20/server/www/redirect.php new file mode 100644 index 00000000..8fc9cd64 --- /dev/null +++ b/hs20/server/www/redirect.php @@ -0,0 +1,32 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_GET["id"]); +else + $id = 0; + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; + +header("Location: $uri", true, 302); + +$user = $row['user']; +$realm = $row['realm']; + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'redirected after user input')"); + +?> diff --git a/hs20/server/www/remediation.php b/hs20/server/www/remediation.php new file mode 100644 index 00000000..392a7bd7 --- /dev/null +++ b/hs20/server/www/remediation.php @@ -0,0 +1,18 @@ +<html> +<head> +<title>Hotspot 2.0 subscription remediation</title> +</head> +<body> + +<?php + +echo "SessionID: " . $_GET["session_id"] . "<br>\n"; + +echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Complete user subscription remediation</a><br>\n"; + +?> + +This will provide a new machine-generated password. + +</body> +</html> diff --git a/hs20/server/www/signup.php b/hs20/server/www/signup.php new file mode 100644 index 00000000..a6267047 --- /dev/null +++ b/hs20/server/www/signup.php @@ -0,0 +1,46 @@ +<html> +<head> +<title>Hotspot 2.0 signup</title> +</head> +<body> + +<?php + +$id = $_GET["session_id"]; + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +$row = $db->query("SELECT realm FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$realm = $row['realm']; + +echo "<h3>Sign up for a subscription - $realm</h3>\n"; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if ($row && strlen($row['value']) > 0) { + echo "<p><a href=\"free.php?session_id=$id\">Sign up for free access</a></p>\n"; +} + +echo "<form action=\"add-mo.php\" method=\"POST\">\n"; +echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n"; +?> +Select a username and password. Leave password empty to get automatically +generated and machine managed password.<br> +Username: <input type="text" name="user"><br> +Password: <input type="password" name="password"><br> +<input type="submit" value="Complete subscription registration"> +</form> + +<?php +echo "<p><a href=\"cert-enroll.php?id=$id\">Enroll a client certificate</a></p>\n" +?> + +</body> +</html> diff --git a/hs20/server/www/spp.php b/hs20/server/www/spp.php new file mode 100644 index 00000000..dde4434d --- /dev/null +++ b/hs20/server/www/spp.php @@ -0,0 +1,127 @@ +<?php + +require('config.php'); + +if (!stristr($_SERVER["CONTENT_TYPE"], "application/soap+xml")) { + error_log("spp.php - Unexpected Content-Type " . $_SERVER["CONTENT_TYPE"]); + die("Unexpected Content-Type"); +} + +if ($_SERVER["REQUEST_METHOD"] != "POST") { + error_log("spp.php - Unexpected method " . $_SERVER["REQUEST_METHOD"]); + die("Unexpected method"); +} + +if (isset($_GET["realm"])) { + $realm = $_GET["realm"]; + $realm = PREG_REPLACE("/[^0-9a-zA-Z\.\-]/i", '', $realm); +} else { + error_log("spp.php - Realm not specified"); + die("Realm not specified"); +} + +unset($user); +putenv("HS20CERT"); + +if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { + $needed = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("spp.php - Authentication failed - missing: " . print_r($needed)); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("spp.php - Authentication failed - empty username"); + die('Authentication failed'); + } + + + $db = new PDO($osu_db); + if (!$db) { + error_log("spp.php - Could not access database"); + die("Could not access database"); + } + $row = $db->query("SELECT password FROM users " . + "WHERE identity='$user' AND realm='$realm'")->fetch(); + if (!$row) { + $row = $db->query("SELECT osu_password FROM users " . + "WHERE osu_user='$user' AND realm='$realm'")->fetch(); + $pw = $row['osu_password']; + } else + $pw = $row['password']; + if (!$row) { + error_log("spp.php - Authentication failed - user '$user' not found"); + die('Authentication failed'); + } + if (strlen($pw) < 1) { + error_log("spp.php - Authentication failed - empty password"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("Authentication failure - response mismatch"); + die('Authentication failed'); + } +} else if (isset($_SERVER["SSL_CLIENT_VERIFY"]) && + $_SERVER["SSL_CLIENT_VERIFY"] == "SUCCESS" && + isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { + $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; + putenv("HS20CERT=yes"); +} else if (!isset($_SERVER["PATH_INFO"]) || + $_SERVER["PATH_INFO"] != "/signup") { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (not signup)"); + die('Authentication required (not signup)'); +} + + +if (isset($user) && strlen($user) > 0) + putenv("HS20USER=$user"); +else + putenv("HS20USER"); + +putenv("HS20REALM=$realm"); +putenv("HS20POST=$HTTP_RAW_POST_DATA"); +$addr = $_SERVER["REMOTE_ADDR"]; +putenv("HS20ADDR=$addr"); + +$last = exec("$osu_root/spp/hs20_spp_server -r$osu_root -f/tmp/hs20_spp_server.log", $output, $ret); + +if ($ret == 2) { + if (empty($_SERVER['PHP_AUTH_DIGEST'])) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (ret 2)"); + die('Authentication required'); + } else { + error_log("spp.php - Unexpected authentication error"); + die("Unexpected authentication error"); + } +} +if ($ret != 0) { + error_log("spp.php - Failed to process SPP request"); + die("Failed to process SPP request"); +} +//error_log("spp.php: Response: " . implode($output)); + +header("Content-Type: application/soap+xml"); + +echo implode($output); + +?> diff --git a/hs20/server/www/users.php b/hs20/server/www/users.php new file mode 100644 index 00000000..c340a33e --- /dev/null +++ b/hs20/server/www/users.php @@ -0,0 +1,349 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) { + $id = $_GET["id"]; + if (!is_numeric($id)) + $id = 0; +} else + $id = 0; +if (isset($_GET["cmd"])) + $cmd = $_GET["cmd"]; +else + $cmd = ''; + +if ($cmd == 'eventlog' && $id > 0) { + $row = $db->query("SELECT dump FROM eventlog WHERE rowid=$id")->fetch(); + $dump = $row['dump']; + if ($dump[0] == '<') { + header("Content-type: text/xml"); + echo "<?xml version=\"1.0\"?>\n"; + echo $dump; + } else { + header("Content-type: text/plain"); + echo $dump; + } + exit; +} + +if ($cmd == 'mo' && $id > 0) { + $mo = $_GET["mo"]; + if (!isset($mo)) + exit; + if ($mo != "devinfo" && $mo != "devdetail" && $mo != "pps") + exit; + $row = $db->query("SELECT $mo FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/xml"); + echo "<?xml version=\"1.0\"?>\n"; + echo $row[$mo]; + exit; +} + +if ($cmd == 'cert' && $id > 0) { + $row = $db->query("SELECT cert_pem FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/plain"); + echo $row['cert_pem']; + exit; +} + +?> + +<html> +<head><title>HS 2.0 users</title></head> +<body> + +<?php + +if ($cmd == 'subrem-clear' && $id > 0) { + $db->exec("UPDATE users SET remediation='' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-user' && $id > 0) { + $db->exec("UPDATE users SET remediation='user' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-machine' && $id > 0) { + $db->exec("UPDATE users SET remediation='machine' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-policy' && $id > 0) { + $db->exec("UPDATE users SET remediation='policy' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-free' && $id > 0) { + $db->exec("UPDATE users SET remediation='free' WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-on' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=1 WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-off' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=0 WHERE rowid=$id"); +} +if ($cmd == 'reset-pw' && $id > 0) { + $db->exec("UPDATE users SET password='ChangeMe' WHERE rowid=$id"); +} +if ($cmd == "policy" && $id > 0 && isset($_GET["policy"])) { + $policy = $_GET["policy"]; + if ($policy == "no-policy" || + is_readable("$osu_root/spp/policy/$policy.xml")) { + $db->exec("UPDATE users SET policy='$policy' WHERE rowid=$id"); + } +} +if ($cmd == "account-type" && $id > 0 && isset($_GET["type"])) { + $type = $_GET["type"]; + if ($type == "shared") + $db->exec("UPDATE users SET shared=1 WHERE rowid=$id"); + if ($type == "default") + $db->exec("UPDATE users SET shared=0 WHERE rowid=$id"); +} + +if ($cmd == "set-osu-cred" && $id > 0) { + $osu_user = $_POST["osu_user"]; + $osu_password = $_POST["osu_password"]; + if (strlen($osu_user) == 0) + $osu_password = ""; + $db->exec("UPDATE users SET osu_user='$osu_user', osu_password='$osu_password' WHERE rowid=$id"); +} + +$dump = 0; + +if ($id > 0) { + +if (isset($_GET["dump"])) { + $dump = $_GET["dump"]; + if (!is_numeric($dump)) + $dump = 0; +} else + $dump = 0; + +echo "[<a href=\"users.php\">All users</a>] "; +if ($dump == 0) + echo "[<a href=\"users.php?id=$id&dump=1\">Include debug dump</a>] "; +else + echo "[<a href=\"users.php?id=$id\">Without debug dump</a>] "; +echo "<br>\n"; + +$row = $db->query("SELECT rowid,* FROM users WHERE rowid=$id")->fetch(); + +echo "<H3>" . $row['identity'] . "@" . $row['realm'] . "</H3>\n"; + +echo "MO: "; +if (strlen($row['devinfo']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=devinfo\">DevInfo</a>]\n"; +} +if (strlen($row['devdetail']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=devdetail\">DevDetail</a>]\n"; +} +if (strlen($row['pps']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=pps\">PPS</a>]\n"; +} +if (strlen($row['cert_pem']) > 0) { + echo "[<a href=\"users.php?cmd=cert&id=$id\">Certificate</a>]\n"; +} +echo "<BR>\n"; + +echo "Fetch PPS MO: "; +if ($row['fetch_pps'] == "1") { + echo "On next connection " . + "[<a href=\"users.php?cmd=fetch-pps-off&id=$id\">" . + "do not fetch</a>]<br>\n"; +} else { + echo "Do not fetch " . + "[<a href=\"users.php?cmd=fetch-pps-on&id=$id\">" . + "request fetch</a>]<br>\n"; +} + +$cert = $row['cert']; +if (strlen($cert) > 0) { + echo "Certificate fingerprint: $cert<br>\n"; +} + +echo "Remediation: "; +$rem = $row['remediation']; +if ($rem == "") { + echo "Not required"; + echo " [<a href=\"users.php?cmd=subrem-add-user&id=" . + $row['rowid'] . "\">add:user</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-machine&id=" . + $row['rowid'] . "\">add:machine</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-policy&id=" . + $row['rowid'] . "\">add:policy</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-free&id=" . + $row['rowid'] . "\">add:free</a>]"; +} else if ($rem == "user") { + echo "User [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else if ($rem == "policy") { + echo "Policy [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else if ($rem == "free") { + echo "Free [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else { + echo "Machine [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} +echo "<br>\n"; + +echo "<form>Policy: <select name=\"policy\" " . + "onChange=\"window.location='users.php?cmd=policy&id=" . + $row['rowid'] . "&policy=' + this.value;\">\n"; +echo "<option value=\"" . $row['policy'] . "\" selected>" . $row['policy'] . + "</option>\n"; +$files = scandir("$osu_root/spp/policy"); +foreach ($files as $file) { + if (!preg_match("/.xml$/", $file)) + continue; + if ($file == $row['policy'] . ".xml") + continue; + $p = substr($file, 0, -4); + echo "<option value=\"$p\">$p</option>\n"; +} +echo "<option value=\"no-policy\">no policy</option>\n"; +echo "</select></form>\n"; + +echo "<form>Account type: <select name=\"type\" " . + "onChange=\"window.location='users.php?cmd=account-type&id=" . + $row['rowid'] . "&type=' + this.value;\">\n"; +if ($row['shared'] > 0) { + $default_sel = ""; + $shared_sel = " selected"; +} else { + $default_sel = " selected"; + $shared_sel = ""; +} +echo "<option value=\"default\"$default_sel>default</option>\n"; +echo "<option value=\"shared\"$shared_sel>shared</option>\n"; +echo "</select></form>\n"; + +echo "Phase 2 method(s): " . $row['methods'] . "<br>\n"; + +echo "<br>\n"; +echo "<a href=\"users.php?cmd=reset-pw&id=" . + $row['rowid'] . "\">Reset AAA password</a><br>\n"; + +echo "<br>\n"; +echo "<form action=\"users.php?cmd=set-osu-cred&id=" . $row['rowid'] . + "\" method=\"POST\">\n"; +echo "OSU credentials (if username empty, AAA credentials are used):<br>\n"; +echo "username: <input type=\"text\" name=\"osu_user\" value=\"" . + $row['osu_user'] . "\">\n"; +echo "password: <input type=\"password\" name=\"osu_password\">\n"; +echo "<input type=\"submit\" value=\"Set OSU credentials\">\n"; +echo "</form>\n"; + +echo "<hr>\n"; + +$user = $row['identity']; +$osu_user = $row['osu_user']; +$realm = $row['realm']; +} + +if ($id > 0 || ($id == 0 && $cmd == 'eventlog')) { + + if ($id == 0) { + echo "[<a href=\"users.php\">All users</a>] "; + echo "<br>\n"; + } + +echo "<table border=1>\n"; +echo "<tr>"; +if ($id == 0) { + echo "<th>user<th>realm"; +} +echo "<th>time<th>address<th>sessionID<th>notes"; +if ($dump > 0) + echo "<th>dump"; +echo "\n"; +if (isset($_GET["limit"])) { + $limit = $_GET["limit"]; + if (!is_numeric($limit)) + $limit = 20; +} else + $limit = 20; +if ($id == 0) + $res = $db->query("SELECT rowid,* FROM eventlog ORDER BY timestamp DESC LIMIT $limit"); +else if (strlen($osu_user) > 0) + $res = $db->query("SELECT rowid,* FROM eventlog WHERE (user='$user' OR user='$osu_user') AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +else + $res = $db->query("SELECT rowid,* FROM eventlog WHERE user='$user' AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +foreach ($res as $row) { + echo "<tr>"; + if ($id == 0) { + echo "<td>" . $row['user'] . "\n"; + echo "<td>" . $row['realm'] . "\n"; + } + echo "<td>" . $row['timestamp'] . "\n"; + echo "<td>" . $row['addr'] . "\n"; + echo "<td>" . $row['sessionid'] . "\n"; + echo "<td>" . $row['notes'] . "\n"; + $d = $row['dump']; + if (strlen($d) > 0) { + echo "[<a href=\"users.php?cmd=eventlog&id=" . $row['rowid'] . + "\">"; + if ($d[0] == '<') + echo "XML"; + else + echo "txt"; + echo "</a>]\n"; + if ($dump > 0) + echo "<td>" . htmlspecialchars($d) . "\n"; + } +} +echo "</table>\n"; + +} + + +if ($id == 0 && $cmd != 'eventlog') { + +echo "[<a href=\"users.php?cmd=eventlog&limit=50\">Eventlog</a>] "; +echo "<br>\n"; + +echo "<table border=1>\n"; +echo "<tr><th>User<th>Realm<th>Remediation<th>Policy<th>Account type<th>Phase 2 method(s)<th>DevId\n"; + +$res = $db->query('SELECT rowid,* FROM users WHERE phase2=1'); +foreach ($res as $row) { + echo "<tr><td><a href=\"users.php?id=" . $row['rowid'] . "\"> " . + $row['identity'] . " </a>"; + echo "<td>" . $row['realm']; + $rem = $row['remediation']; + echo "<td>"; + if ($rem == "") { + echo "Not required"; + } else if ($rem == "user") { + echo "User"; + } else if ($rem == "policy") { + echo "Policy"; + } else if ($rem == "free") { + echo "Free"; + } else { + echo "Machine"; + } + echo "<td>" . $row['policy']; + if ($row['shared'] > 0) + echo "<td>shared"; + else + echo "<td>default"; + echo "<td>" . $row['methods']; + echo "<td>"; + $xml = xml_parser_create(); + xml_parse_into_struct($xml, $row['devinfo'], $devinfo); + foreach($devinfo as $k) { + if ($k['tag'] == 'DEVID') { + echo $k['value']; + break; + } + } + echo "\n"; +} +echo "</table>\n"; + +} + +?> + +</html> diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 5a8e67e9..0a143d39 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -651,12 +651,11 @@ const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, struct hostapd_wpa_psk *psk; int next_ok = prev_psk == NULL; - if (p2p_dev_addr) { + if (p2p_dev_addr && !is_zero_ether_addr(p2p_dev_addr)) { wpa_printf(MSG_DEBUG, "Searching a PSK for " MACSTR " p2p_dev_addr=" MACSTR " prev_psk=%p", MAC2STR(addr), MAC2STR(p2p_dev_addr), prev_psk); - if (!is_zero_ether_addr(p2p_dev_addr)) - addr = NULL; /* Use P2P Device Address for matching */ + addr = NULL; /* Use P2P Device Address for matching */ } else { wpa_printf(MSG_DEBUG, "Searching a PSK for " MACSTR " prev_psk=%p", diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 25eb4903..dfbe6260 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -127,6 +127,7 @@ struct hostapd_eap_user { unsigned int password_hash:1; /* whether password is hashed with * nt_password_hash() */ unsigned int remediation:1; + unsigned int macacl:1; int ttls_auth; /* EAP_TTLS_AUTH_* bitfield */ struct hostapd_radius_attr *accept_attr; }; diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 9abcd7c9..6d3ae5a7 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -774,8 +774,10 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, int mode, int freq, } res = hapd->driver->start_dfs_cac(hapd->drv_priv, &data); - if (!res) + if (!res) { iface->cac_started = 1; + os_get_reltime(&iface->dfs_cac_start); + } return res; } diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 9edaf7d7..7cc9d7de 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -280,4 +280,15 @@ static inline int hostapd_drv_status(struct hostapd_data *hapd, char *buf, return hapd->driver->status(hapd->drv_priv, buf, buflen); } +static inline int hostapd_drv_vendor_cmd(struct hostapd_data *hapd, + int vendor_id, int subcmd, + const u8 *data, size_t data_len, + struct wpabuf *buf) +{ + if (hapd->driver == NULL || hapd->driver->vendor_cmd == NULL) + return -1; + return hapd->driver->vendor_cmd(hapd->drv_priv, vendor_id, subcmd, data, + data_len, buf); +} + #endif /* AP_DRV_OPS */ diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 6e3decd8..86f1cbe1 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -79,6 +79,7 @@ static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity, user->password_hash = eap_user->password_hash; } user->force_version = eap_user->force_version; + user->macacl = eap_user->macacl; user->ttls_auth = eap_user->ttls_auth; user->remediation = eap_user->remediation; user->accept_attr = eap_user->accept_attr; diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c index e1020a67..97609334 100644 --- a/src/ap/ctrl_iface_ap.c +++ b/src/ap/ctrl_iface_ap.c @@ -423,6 +423,28 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, return len; len += ret; + if (!iface->cac_started || !iface->dfs_cac_ms) { + ret = os_snprintf(buf + len, buflen - len, + "cac_time_seconds=%d\n" + "cac_time_left_seconds=N/A\n", + iface->dfs_cac_ms / 1000); + } else { + /* CAC started and CAC time set - calculate remaining time */ + struct os_reltime now; + unsigned int left_time; + + os_reltime_age(&iface->dfs_cac_start, &now); + left_time = iface->dfs_cac_ms / 1000 - now.sec; + ret = os_snprintf(buf + len, buflen - len, + "cac_time_seconds=%u\n" + "cac_time_left_seconds=%u\n", + iface->dfs_cac_ms / 1000, + left_time); + } + if (ret < 0 || (size_t) ret >= buflen - len) + return len; + len += ret; + ret = os_snprintf(buf + len, buflen - len, "channel=%u\n" "secondary_channel=%d\n" diff --git a/src/ap/dfs.c b/src/ap/dfs.c index 0f262ceb..3fb18810 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -573,6 +573,28 @@ static int dfs_are_channels_overlapped(struct hostapd_iface *iface, int freq, } +static unsigned int dfs_get_cac_time(struct hostapd_iface *iface, + int start_chan_idx, int n_chans) +{ + struct hostapd_channel_data *channel; + struct hostapd_hw_modes *mode; + int i; + unsigned int cac_time_ms = 0; + + mode = iface->current_mode; + + for (i = 0; i < n_chans; i++) { + channel = &mode->channels[start_chan_idx + i]; + if (!(channel->flag & HOSTAPD_CHAN_RADAR)) + continue; + if (channel->dfs_cac_ms > cac_time_ms) + cac_time_ms = channel->dfs_cac_ms; + } + + return cac_time_ms; +} + + /* * Main DFS handler * 1 - continue channel/ap setup @@ -596,6 +618,10 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) /* Get number of used channels, depend on width */ n_chans = dfs_get_used_n_chans(iface); + /* Setup CAC time */ + iface->dfs_cac_ms = dfs_get_cac_time(iface, start_chan_idx, + n_chans); + /* Check if any of configured channels require DFS */ res = dfs_check_chans_radar(iface, start_chan_idx, n_chans); wpa_printf(MSG_DEBUG, @@ -640,12 +666,13 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) hostapd_set_state(iface, HAPD_IFACE_DFS); wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", iface->freq); wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START - "freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d", + "freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d, cac_time=%ds", iface->freq, iface->conf->channel, iface->conf->secondary_channel, iface->conf->vht_oper_chwidth, iface->conf->vht_oper_centr_freq_seg0_idx, - iface->conf->vht_oper_centr_freq_seg1_idx); + iface->conf->vht_oper_centr_freq_seg1_idx, + iface->dfs_cac_ms / 1000); res = hostapd_start_dfs_cac(iface, iface->conf->hw_mode, iface->freq, diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index ed2226ca..614a5bf4 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -172,6 +172,7 @@ int hostapd_reload_config(struct hostapd_iface *iface) hapd = iface->bss[j]; hapd->iconf = newconf; hapd->iconf->channel = oldconf->channel; + hapd->iconf->secondary_channel = oldconf->secondary_channel; hapd->iconf->ieee80211n = oldconf->ieee80211n; hapd->iconf->ieee80211ac = oldconf->ieee80211ac; hapd->iconf->ht_capab = oldconf->ht_capab; diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 9a705a45..090544d5 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -348,6 +348,9 @@ struct hostapd_iface { unsigned int cs_c_off_proberesp; int csa_in_progress; + unsigned int dfs_cac_ms; + struct os_reltime dfs_cac_start; + #ifdef CONFIG_ACS unsigned int acs_num_completed_scans; #endif /* CONFIG_ACS */ diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index b12c9d6c..035415f2 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -1787,6 +1787,7 @@ static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity, user->password_hash = eap_user->password_hash; } user->force_version = eap_user->force_version; + user->macacl = eap_user->macacl; user->ttls_auth = eap_user->ttls_auth; user->remediation = eap_user->remediation; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 59352739..02ade125 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -93,6 +93,9 @@ struct hostapd_channel_data { */ long double interference_factor; #endif /* CONFIG_ACS */ + + /* DFS CAC time in milliseconds */ + unsigned int dfs_cac_ms; }; #define HOSTAPD_MODE_FLAG_HT_INFO_KNOWN BIT(0) @@ -1269,6 +1272,13 @@ struct csa_settings { u16 counter_offset_presp; }; +/* TDLS peer capabilities for send_tdls_mgmt() */ +enum tdls_peer_capability { + TDLS_PEER_HT = BIT(0), + TDLS_PEER_VHT = BIT(1), + TDLS_PEER_WMM = BIT(2), +}; + /** * struct wpa_driver_ops - Driver interface API definition * @@ -2449,6 +2459,7 @@ struct wpa_driver_ops { * @action_code: TDLS action code for the mssage * @dialog_token: Dialog Token to use in the message (if needed) * @status_code: Status Code or Reason Code to use (if needed) + * @peer_capab: TDLS peer capability (TDLS_PEER_* bitfield) * @buf: TDLS IEs to add to the message * @len: Length of buf in octets * Returns: 0 on success, negative (<0) on failure @@ -2457,7 +2468,7 @@ struct wpa_driver_ops { * responsible for receiving and sending all TDLS packets. */ int (*send_tdls_mgmt)(void *priv, const u8 *dst, u8 action_code, - u8 dialog_token, u16 status_code, + u8 dialog_token, u16 status_code, u32 peer_capab, const u8 *buf, size_t len); /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 3ecce191..27b4c48e 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -6200,6 +6200,7 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, u8 channel; chan->freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); chan->flag = 0; + chan->dfs_cac_ms = 0; if (ieee80211_freq_to_chan(chan->freq, &channel) != NUM_HOSTAPD_MODES) chan->chan = channel; @@ -6226,6 +6227,11 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, break; } } + + if (tb_freq[NL80211_FREQUENCY_ATTR_DFS_CAC_TIME]) { + chan->dfs_cac_ms = nla_get_u32( + tb_freq[NL80211_FREQUENCY_ATTR_DFS_CAC_TIME]); + } } @@ -7660,6 +7666,16 @@ static int nl80211_create_iface(struct wpa_driver_nl80211_data *drv, if (use_existing) { wpa_printf(MSG_DEBUG, "nl80211: Continue using existing interface %s", ifname); + if (addr && iftype != NL80211_IFTYPE_MONITOR && + linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, + addr) < 0 && + (linux_set_iface_flags(drv->global->ioctl_sock, + ifname, 0) < 0 || + linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, + addr) < 0 || + linux_set_iface_flags(drv->global->ioctl_sock, + ifname, 1) < 0)) + return -1; return -ENFILE; } wpa_printf(MSG_INFO, "Try to remove and re-create %s", ifname); @@ -9446,6 +9462,29 @@ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, } +static void dump_ifidx(struct wpa_driver_nl80211_data *drv) +{ + char buf[200], *pos, *end; + int i, res; + + pos = buf; + end = pos + sizeof(buf); + + for (i = 0; i < drv->num_if_indices; i++) { + if (!drv->if_indices[i]) + continue; + res = os_snprintf(pos, end - pos, " %d", drv->if_indices[i]); + if (res < 0 || res >= end - pos) + break; + pos += res; + } + *pos = '\0'; + + wpa_printf(MSG_DEBUG, "nl80211: if_indices[%d]:%s", + drv->num_if_indices, buf); +} + + static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) { int i; @@ -9453,9 +9492,15 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) wpa_printf(MSG_DEBUG, "nl80211: Add own interface ifindex %d", ifidx); + if (have_ifidx(drv, ifidx)) { + wpa_printf(MSG_DEBUG, "nl80211: ifindex %d already in the list", + ifidx); + return; + } for (i = 0; i < drv->num_if_indices; i++) { if (drv->if_indices[i] == 0) { drv->if_indices[i] = ifidx; + dump_ifidx(drv); return; } } @@ -9481,6 +9526,7 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) sizeof(drv->default_if_indices)); drv->if_indices[drv->num_if_indices] = ifidx; drv->num_if_indices++; + dump_ifidx(drv); } @@ -9494,6 +9540,7 @@ static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) break; } } + dump_ifidx(drv); } @@ -9929,6 +9976,9 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, if (drv->global) drv->global->if_add_ifindex = ifidx; + if (ifidx > 0) + add_ifidx(drv, ifidx); + return 0; } @@ -9944,6 +9994,8 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss, __func__, type, ifname, ifindex, bss->added_if); if (ifindex > 0 && (bss->added_if || bss->ifindex != ifindex)) nl80211_remove_iface(drv, ifindex); + else if (ifindex > 0 && !bss->added_if) + del_ifidx(drv, ifindex); if (type != WPA_IF_AP_BSS) return 0; @@ -9972,6 +10024,8 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss, /* Unsubscribe management frames */ nl80211_teardown_ap(bss); nl80211_destroy_bss(bss); + if (!bss->added_if) + i802_set_iface_flags(bss, 0); os_free(bss); bss = NULL; break; @@ -11176,7 +11230,7 @@ nla_put_failure: static int nl80211_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code, u8 dialog_token, u16 status_code, - const u8 *buf, size_t len) + u32 peer_capab, const u8 *buf, size_t len) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -11198,6 +11252,15 @@ static int nl80211_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code, NLA_PUT_U8(msg, NL80211_ATTR_TDLS_ACTION, action_code); NLA_PUT_U8(msg, NL80211_ATTR_TDLS_DIALOG_TOKEN, dialog_token); NLA_PUT_U16(msg, NL80211_ATTR_STATUS_CODE, status_code); + if (peer_capab) { + /* + * The internal enum tdls_peer_capability definition is + * currently identical with the nl80211 enum + * nl80211_tdls_peer_capability, so no conversion is needed + * here. + */ + NLA_PUT_U32(msg, NL80211_ATTR_TDLS_PEER_CAPABILITY, peer_capab); + } NLA_PUT(msg, NL80211_ATTR_IE, len, buf); return send_and_recv_msgs(drv, msg, NULL, NULL); diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h index a12e6cae..1ba9d626 100644 --- a/src/drivers/nl80211_copy.h +++ b/src/drivers/nl80211_copy.h @@ -303,8 +303,9 @@ * passed, all channels allowed for the current regulatory domain * are used. Extra IEs can also be passed from the userspace by * using the %NL80211_ATTR_IE attribute. - * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan. Returns -ENOENT - * if scheduled scan is not running. + * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan. Returns -ENOENT if + * scheduled scan is not running. The caller may assume that as soon + * as the call returns, it is safe to start a new scheduled scan again. * @NL80211_CMD_SCHED_SCAN_RESULTS: indicates that there are scheduled scan * results available. * @NL80211_CMD_SCHED_SCAN_STOPPED: indicates that the scheduled scan has @@ -1575,6 +1576,9 @@ enum nl80211_commands { * advertise values that cannot always be met. In such cases, an attempt * to add a new station entry with @NL80211_CMD_NEW_STATION may fail. * + * @NL80211_ATTR_TDLS_PEER_CAPABILITY: flags for TDLS peer capabilities, u32. + * As specified in the &enum nl80211_tdls_peer_capability. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1908,6 +1912,8 @@ enum nl80211_attrs { NL80211_ATTR_MAX_AP_ASSOC_STA, + NL80211_ATTR_TDLS_PEER_CAPABILITY, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -2329,6 +2335,7 @@ enum nl80211_band_attr { * @NL80211_FREQUENCY_ATTR_NO_160MHZ: any 160 MHz (but not 80+80) channel * using this channel as the primary or any of the secondary channels * isn't possible + * @NL80211_FREQUENCY_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -2347,6 +2354,7 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_HT40_PLUS, NL80211_FREQUENCY_ATTR_NO_80MHZ, NL80211_FREQUENCY_ATTR_NO_160MHZ, + NL80211_FREQUENCY_ATTR_DFS_CAC_TIME, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -2437,15 +2445,14 @@ enum nl80211_reg_type { * in KHz. This is not a center a frequency but an actual regulatory * band edge. * @NL80211_ATTR_FREQ_RANGE_MAX_BW: maximum allowed bandwidth for this - * frequency range, in KHz. If not present or 0, maximum available - * bandwidth should be calculated base on contiguous rules and wider - * channels will be allowed to cross multiple contiguous/overlapping - * frequency ranges. + * frequency range, in KHz. * @NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN: the maximum allowed antenna gain * for a given frequency range. The value is in mBi (100 * dBi). * If you don't have one then don't send this. * @NL80211_ATTR_POWER_RULE_MAX_EIRP: the maximum allowed EIRP for * a given frequency range. The value is in mBm (100 * dBm). + * @NL80211_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds. + * If not present or 0 default CAC time will be used. * @NL80211_REG_RULE_ATTR_MAX: highest regulatory rule attribute number * currently defined * @__NL80211_REG_RULE_ATTR_AFTER_LAST: internal use @@ -2461,6 +2468,8 @@ enum nl80211_reg_rule_attr { NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN, NL80211_ATTR_POWER_RULE_MAX_EIRP, + NL80211_ATTR_DFS_CAC_TIME, + /* keep last */ __NL80211_REG_RULE_ATTR_AFTER_LAST, NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1 @@ -2511,6 +2520,9 @@ enum nl80211_sched_scan_match_attr { * @NL80211_RRF_NO_IR: no mechanisms that initiate radiation are allowed, * this includes probe requests or modes of operation that require * beaconing. + * @NL80211_RRF_AUTO_BW: maximum available bandwidth should be calculated + * base on contiguous rules and wider channels will be allowed to cross + * multiple contiguous/overlapping frequency ranges. */ enum nl80211_reg_rule_flags { NL80211_RRF_NO_OFDM = 1<<0, @@ -2522,6 +2534,7 @@ enum nl80211_reg_rule_flags { NL80211_RRF_PTMP_ONLY = 1<<6, NL80211_RRF_NO_IR = 1<<7, __NL80211_RRF_NO_IBSS = 1<<8, + NL80211_RRF_AUTO_BW = 1<<11, }; #define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR @@ -3843,11 +3856,6 @@ enum nl80211_ap_sme_features { * @NL80211_FEATURE_CELL_BASE_REG_HINTS: This driver has been tested * to work properly to suppport receiving regulatory hints from * cellular base stations. - * @NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL: If this is set, an active - * P2P Device (%NL80211_IFTYPE_P2P_DEVICE) requires its own channel - * in the interface combinations, even when it's only used for scan - * and remain-on-channel. This could be due to, for example, the - * remain-on-channel implementation requiring a channel context. * @NL80211_FEATURE_SAE: This driver supports simultaneous authentication of * equals (SAE) with user space SME (NL80211_CMD_AUTHENTICATE) in station * mode @@ -3889,7 +3897,7 @@ enum nl80211_feature_flags { NL80211_FEATURE_HT_IBSS = 1 << 1, NL80211_FEATURE_INACTIVITY_TIMER = 1 << 2, NL80211_FEATURE_CELL_BASE_REG_HINTS = 1 << 3, - NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL = 1 << 4, + /* bit 4 is reserved - don't use */ NL80211_FEATURE_SAE = 1 << 5, NL80211_FEATURE_LOW_PRIORITY_SCAN = 1 << 6, NL80211_FEATURE_SCAN_FLUSH = 1 << 7, @@ -4079,4 +4087,20 @@ struct nl80211_vendor_cmd_info { __u32 subcmd; }; +/** + * enum nl80211_tdls_peer_capability - TDLS peer flags. + * + * Used by tdls_mgmt() to determine which conditional elements need + * to be added to TDLS Setup frames. + * + * @NL80211_TDLS_PEER_HT: TDLS peer is HT capable. + * @NL80211_TDLS_PEER_VHT: TDLS peer is VHT capable. + * @NL80211_TDLS_PEER_WMM: TDLS peer is WMM capable. + */ +enum nl80211_tdls_peer_capability { + NL80211_TDLS_PEER_HT = 1<<0, + NL80211_TDLS_PEER_VHT = 1<<1, + NL80211_TDLS_PEER_WMM = 1<<2, +}; + #endif /* __LINUX_NL80211_H */ diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index 698a5ac0..1253bd6e 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -33,6 +33,7 @@ struct eap_user { int phase2; int force_version; unsigned int remediation:1; + unsigned int macacl:1; int ttls_auth; /* bitfield of * EAP_TTLS_AUTH_{PAP,CHAP,MSCHAP,MSCHAPV2} */ struct hostapd_radius_attr *accept_attr; diff --git a/src/radius/radius.c b/src/radius/radius.c index 370b517f..47b4f8af 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -1247,30 +1247,28 @@ int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, } -/* Add User-Password attribute to a RADIUS message and encrypt it as specified - * in RFC 2865, Chap. 5.2 */ -struct radius_attr_hdr * -radius_msg_add_attr_user_password(struct radius_msg *msg, - const u8 *data, size_t data_len, - const u8 *secret, size_t secret_len) +int radius_user_password_hide(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len, + u8 *buf, size_t buf_len) { - u8 buf[128]; - size_t padlen, i, buf_len, pos; + size_t padlen, i, pos; const u8 *addr[2]; size_t len[2]; u8 hash[16]; - if (data_len > 128) - return NULL; + if (data_len + 16 > buf_len) + return -1; os_memcpy(buf, data, data_len); - buf_len = data_len; padlen = data_len % 16; - if (padlen && data_len < sizeof(buf)) { + if (padlen && data_len < buf_len) { padlen = 16 - padlen; os_memset(buf + data_len, 0, padlen); - buf_len += padlen; + buf_len = data_len + padlen; + } else { + buf_len = data_len; } addr[0] = secret; @@ -1296,8 +1294,27 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, pos += 16; } + return buf_len; +} + + +/* Add User-Password attribute to a RADIUS message and encrypt it as specified + * in RFC 2865, Chap. 5.2 */ +struct radius_attr_hdr * +radius_msg_add_attr_user_password(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len) +{ + u8 buf[128]; + int res; + + res = radius_user_password_hide(msg, data, data_len, + secret, secret_len, buf, sizeof(buf)); + if (res < 0) + return NULL; + return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD, - buf, buf_len); + buf, res); } diff --git a/src/radius/radius.h b/src/radius/radius.h index d8bf21eb..d388f717 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -251,6 +251,10 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg, const u8 *recv_key, size_t recv_key_len); int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, size_t len); +int radius_user_password_hide(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len, + u8 *buf, size_t buf_len); struct radius_attr_hdr * radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *data, size_t data_len, diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index f2ea3932..bd358ae9 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -86,6 +86,7 @@ struct radius_session { u8 last_authenticator[16]; unsigned int remediation:1; + unsigned int macacl:1; struct hostapd_radius_attr *accept_attr; }; @@ -636,6 +637,7 @@ radius_server_get_new_session(struct radius_server_data *data, return NULL; } sess->accept_attr = tmp.accept_attr; + sess->macacl = tmp.macacl; sess->username = os_malloc(user_len * 2 + 1); if (sess->username == NULL) { @@ -823,6 +825,87 @@ radius_server_encapsulate_eap(struct radius_server_data *data, } +static struct radius_msg * +radius_server_macacl(struct radius_server_data *data, + struct radius_client *client, + struct radius_session *sess, + struct radius_msg *request) +{ + struct radius_msg *msg; + int code; + struct radius_hdr *hdr = radius_msg_get_hdr(request); + u8 *pw; + size_t pw_len; + + code = RADIUS_CODE_ACCESS_ACCEPT; + + if (radius_msg_get_attr_ptr(request, RADIUS_ATTR_USER_PASSWORD, &pw, + &pw_len, NULL) < 0) { + RADIUS_DEBUG("Could not get User-Password"); + code = RADIUS_CODE_ACCESS_REJECT; + } else { + int res; + struct eap_user tmp; + + os_memset(&tmp, 0, sizeof(tmp)); + res = data->get_eap_user(data->conf_ctx, (u8 *) sess->username, + os_strlen(sess->username), 0, &tmp); + if (res || !tmp.macacl || tmp.password == NULL) { + RADIUS_DEBUG("No MAC ACL user entry"); + os_free(tmp.password); + code = RADIUS_CODE_ACCESS_REJECT; + } else { + u8 buf[128]; + res = radius_user_password_hide( + request, tmp.password, tmp.password_len, + (u8 *) client->shared_secret, + client->shared_secret_len, + buf, sizeof(buf)); + os_free(tmp.password); + + if (res < 0 || pw_len != (size_t) res || + os_memcmp(pw, buf, res) != 0) { + RADIUS_DEBUG("Incorrect User-Password"); + code = RADIUS_CODE_ACCESS_REJECT; + } + } + } + + msg = radius_msg_new(code, hdr->identifier); + if (msg == NULL) { + RADIUS_DEBUG("Failed to allocate reply message"); + return NULL; + } + + if (radius_msg_copy_attr(msg, request, RADIUS_ATTR_PROXY_STATE) < 0) { + RADIUS_DEBUG("Failed to copy Proxy-State attribute(s)"); + radius_msg_free(msg); + return NULL; + } + + if (code == RADIUS_CODE_ACCESS_ACCEPT) { + struct hostapd_radius_attr *attr; + for (attr = sess->accept_attr; attr; attr = attr->next) { + if (!radius_msg_add_attr(msg, attr->type, + wpabuf_head(attr->val), + wpabuf_len(attr->val))) { + wpa_printf(MSG_ERROR, "Could not add RADIUS attribute"); + radius_msg_free(msg); + return NULL; + } + } + } + + if (radius_msg_finish_srv(msg, (u8 *) client->shared_secret, + client->shared_secret_len, + hdr->authenticator) < 0) { + RADIUS_DEBUG("Failed to add Message-Authenticator attribute"); + } + + return msg; +} + + static int radius_server_reject(struct radius_server_data *data, struct radius_client *client, struct radius_msg *request, @@ -958,6 +1041,12 @@ static int radius_server_request(struct radius_server_data *data, } eap = radius_msg_get_eap(msg); + if (eap == NULL && sess->macacl) { + reply = radius_server_macacl(data, client, sess, msg); + if (reply == NULL) + return -1; + goto send_reply; + } if (eap == NULL) { RADIUS_DEBUG("No EAP-Message in RADIUS packet from %s", from_addr); @@ -1015,6 +1104,7 @@ static int radius_server_request(struct radius_server_data *data, reply = radius_server_encapsulate_eap(data, client, sess, msg); +send_reply: if (reply) { struct wpabuf *buf; struct radius_hdr *hdr; @@ -1904,6 +1994,7 @@ static int radius_server_get_eap_user(void *ctx, const u8 *identity, if (ret == 0 && user) { sess->accept_attr = user->accept_attr; sess->remediation = user->remediation; + sess->macacl = user->macacl; } return ret; } diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c index 9b8ca6b8..62a2a591 100644 --- a/src/rsn_supp/tdls.c +++ b/src/rsn_supp/tdls.c @@ -118,6 +118,7 @@ struct wpa_tdls_peer { u8 action_code; /* TDLS frame type */ u8 dialog_token; u16 status_code; + u32 peer_capab; int buf_len; /* length of TPK message for retransmission */ u8 *buf; /* buffer for TPK message */ } sm_tmr; @@ -142,6 +143,8 @@ struct wpa_tdls_peer { u8 *supp_oper_classes; size_t supp_oper_classes_len; + + u8 wmm_capable; }; @@ -211,15 +214,16 @@ static int wpa_tdls_set_key(struct wpa_sm *sm, struct wpa_tdls_peer *peer) static int wpa_tdls_send_tpk_msg(struct wpa_sm *sm, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { return wpa_sm_send_tdls_mgmt(sm, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, buf, len); } static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, - u8 dialog_token, u16 status_code, + u8 dialog_token, u16 status_code, u32 peer_capab, const u8 *msg, size_t msg_len) { struct wpa_tdls_peer *peer; @@ -230,7 +234,7 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, (unsigned int) msg_len); if (wpa_tdls_send_tpk_msg(sm, dest, action_code, dialog_token, - status_code, msg, msg_len)) { + status_code, peer_capab, msg, msg_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to send message " "(action_code=%u)", action_code); return -1; @@ -268,6 +272,7 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, peer->sm_tmr.action_code = action_code; peer->sm_tmr.dialog_token = dialog_token; peer->sm_tmr.status_code = status_code; + peer->sm_tmr.peer_capab = peer_capab; peer->sm_tmr.buf_len = msg_len; os_free(peer->sm_tmr.buf); peer->sm_tmr.buf = os_malloc(msg_len); @@ -324,6 +329,7 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) peer->sm_tmr.action_code, peer->sm_tmr.dialog_token, peer->sm_tmr.status_code, + peer->sm_tmr.peer_capab, peer->sm_tmr.buf, peer->sm_tmr.buf_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to retry " @@ -645,6 +651,8 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer) peer->supp_oper_classes = NULL; peer->rsnie_i_len = peer->rsnie_p_len = 0; peer->cipher = 0; + peer->qos_info = 0; + peer->wmm_capable = 0; peer->tpk_set = peer->tpk_success = 0; os_memset(&peer->tpk, 0, sizeof(peer->tpk)); os_memset(peer->inonce, 0, WPA_NONCE_LEN); @@ -747,7 +755,7 @@ skip_ies: /* request driver to send Teardown using this FTIE */ wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0, - reason_code, rbuf, pos - rbuf); + reason_code, 0, rbuf, pos - rbuf); os_free(rbuf); /* clear the Peerkey statemachine */ @@ -918,7 +926,7 @@ static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst, " (action=%u status=%u)", MAC2STR(dst), tdls_action, status); return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status, - NULL, 0); + 0, NULL, 0); } @@ -1124,7 +1132,7 @@ skip_ies: MAC2STR(peer->addr)); status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, - 1, 0, rbuf, pos - rbuf); + 1, 0, 0, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1208,7 +1216,7 @@ static int wpa_tdls_send_tpk_m2(struct wpa_sm *sm, skip_ies: status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, - dtoken, 0, rbuf, pos - rbuf); + dtoken, 0, 0, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1226,6 +1234,7 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, struct wpa_tdls_timeoutie timeoutie; u32 lifetime; int status; + u32 peer_capab = 0; buf_len = 0; if (wpa_tdls_get_privacy(sm)) { @@ -1288,9 +1297,16 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, wpa_tdls_ftie_mic(peer->tpk.kck, 3, (u8 *) lnkid, peer->rsnie_p, (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); + if (peer->vht_capabilities) + peer_capab |= TDLS_PEER_VHT; + else if (peer->ht_capabilities) + peer_capab |= TDLS_PEER_HT; + else if (peer->wmm_capable) + peer_capab |= TDLS_PEER_WMM; + skip_ies: status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, - dtoken, 0, rbuf, pos - rbuf); + dtoken, 0, peer_capab, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1305,7 +1321,7 @@ static int wpa_tdls_send_discovery_response(struct wpa_sm *sm, "(peer " MACSTR ")", MAC2STR(peer->addr)); return wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE, - dialog_token, 0, NULL, 0); + dialog_token, 0, 0, NULL, 0); } @@ -1366,7 +1382,7 @@ int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr) wpa_printf(MSG_DEBUG, "TDLS: Sending Discovery Request to peer " MACSTR, MAC2STR(addr)); return wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_DISCOVERY_REQUEST, - 1, 0, NULL, 0); + 1, 0, 0, NULL, 0); } @@ -1484,6 +1500,8 @@ static int copy_peer_wmm_capab(const struct wpa_eapol_ie_parse *kde, wmm = (struct wmm_information_element *) kde->wmm; peer->qos_info = wmm->qos_info; + peer->wmm_capable = 1; + wpa_printf(MSG_DEBUG, "TDLS: Peer WMM QOS Info 0x%x", peer->qos_info); return 0; } diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index 20b3f621..df103422 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -54,7 +54,8 @@ struct wpa_sm_ctx { int *tdls_ext_setup); int (*send_tdls_mgmt)(void *ctx, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, size_t len); + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len); int (*tdls_oper)(void *ctx, int oper, const u8 *peer); int (*tdls_peer_addset)(void *ctx, const u8 *addr, int add, u16 aid, u16 capability, const u8 *supp_rates, diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index 75cfb479..f2fd2857 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -267,13 +267,13 @@ static inline int wpa_sm_tdls_get_capa(struct wpa_sm *sm, static inline int wpa_sm_send_tdls_mgmt(struct wpa_sm *sm, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, - size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { if (sm->ctx->send_tdls_mgmt) return sm->ctx->send_tdls_mgmt(sm->ctx->ctx, dst, action_code, dialog_token, status_code, - buf, len); + peer_capab, buf, len); return -1; } diff --git a/src/utils/edit.c b/src/utils/edit.c index 177ecf41..d340bfad 100644 --- a/src/utils/edit.c +++ b/src/utils/edit.c @@ -14,7 +14,7 @@ #include "list.h" #include "edit.h" -#define CMD_BUF_LEN 256 +#define CMD_BUF_LEN 4096 static char cmdbuf[CMD_BUF_LEN]; static int cmdbuf_pos = 0; static int cmdbuf_len = 0; diff --git a/src/utils/edit_simple.c b/src/utils/edit_simple.c index a095ea6a..13173cb1 100644 --- a/src/utils/edit_simple.c +++ b/src/utils/edit_simple.c @@ -13,7 +13,7 @@ #include "edit.h" -#define CMD_BUF_LEN 256 +#define CMD_BUF_LEN 4096 static char cmdbuf[CMD_BUF_LEN]; static int cmdbuf_pos = 0; static const char *ps2 = NULL; diff --git a/src/utils/eloop.c b/src/utils/eloop.c index f83a2327..2667c8c9 100644 --- a/src/utils/eloop.c +++ b/src/utils/eloop.c @@ -7,6 +7,7 @@ */ #include "includes.h" +#include <assert.h> #include "common.h" #include "trace.h" @@ -14,7 +15,6 @@ #include "eloop.h" #ifdef CONFIG_ELOOP_POLL -#include <assert.h> #include <poll.h> #endif /* CONFIG_ELOOP_POLL */ @@ -374,8 +374,10 @@ static void eloop_sock_table_set_fds(struct eloop_sock_table *table, if (table->table == NULL) return; - for (i = 0; i < table->count; i++) + for (i = 0; i < table->count; i++) { + assert(table->table[i].sock >= 0); FD_SET(table->table[i].sock, fds); + } } @@ -459,6 +461,7 @@ int eloop_register_sock(int sock, eloop_event_type type, { struct eloop_sock_table *table; + assert(sock >= 0); table = eloop_get_sock_table(type); return eloop_sock_table_add_sock(table, sock, handler, eloop_data, user_data); diff --git a/src/utils/xml-utils.h b/src/utils/xml-utils.h index 0d8e0cb8..fb6208cd 100644 --- a/src/utils/xml-utils.h +++ b/src/utils/xml-utils.h @@ -73,9 +73,6 @@ for (; \ if (!xml_node_is_element(ctx, child)) \ continue -typedef void (*debug_print_func)(void *ctx, int print, const char *fmt, ...) - __attribute__ ((format (printf, 3, 4))); - struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx, const void *env); diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 23aab4ba..6a462102 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -1701,6 +1701,7 @@ static const struct parse_data ssid_fields[] = { { INT_RANGE(disable_ht, 0, 1) }, { INT_RANGE(disable_ht40, -1, 1) }, { INT_RANGE(disable_sgi, 0, 1) }, + { INT_RANGE(disable_ldpc, 0, 1) }, { INT_RANGE(disable_max_amsdu, -1, 1) }, { INT_RANGE(ampdu_factor, -1, 3) }, { INT_RANGE(ampdu_density, -1, 7) }, @@ -2157,6 +2158,7 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid) ssid->disable_ht = DEFAULT_DISABLE_HT; ssid->disable_ht40 = DEFAULT_DISABLE_HT40; ssid->disable_sgi = DEFAULT_DISABLE_SGI; + ssid->disable_ldpc = DEFAULT_DISABLE_LDPC; ssid->disable_max_amsdu = DEFAULT_DISABLE_MAX_AMSDU; ssid->ampdu_factor = DEFAULT_AMPDU_FACTOR; ssid->ampdu_density = DEFAULT_AMPDU_DENSITY; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 4f581301..73945931 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -711,6 +711,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) INT_DEF(eap_workaround, DEFAULT_EAP_WORKAROUND); STR(pac_file); INT_DEFe(fragment_size, DEFAULT_FRAGMENT_SIZE); + INTe(ocsp); #endif /* IEEE8021X_EAPOL */ INT(mode); INT(frequency); diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index d515030f..71829eff 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -30,6 +30,7 @@ #define DEFAULT_DISABLE_HT 0 #define DEFAULT_DISABLE_HT40 0 #define DEFAULT_DISABLE_SGI 0 +#define DEFAULT_DISABLE_LDPC 0 #define DEFAULT_DISABLE_MAX_AMSDU -1 /* no change */ #define DEFAULT_AMPDU_FACTOR -1 /* no change */ #define DEFAULT_AMPDU_DENSITY -1 /* no change */ @@ -525,6 +526,14 @@ struct wpa_ssid { int disable_sgi; /** + * disable_ldpc - Disable LDPC for this network + * + * By default, use it if it is available, but this can be configured + * to 1 to have it disabled. + */ + int disable_ldpc; + + /** * disable_max_amsdu - Disable MAX A-MSDU * * A-MDSU will be 3839 bytes when disabled, or 7935 diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index bbcd6620..938ece69 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -532,12 +532,14 @@ static inline int wpa_drv_ampdu(struct wpa_supplicant *wpa_s, int ampdu) static inline int wpa_drv_send_tdls_mgmt(struct wpa_supplicant *wpa_s, const u8 *dst, u8 action_code, u8 dialog_token, u16 status_code, - const u8 *buf, size_t len) + u32 peer_capab, const u8 *buf, + size_t len) { if (wpa_s->driver->send_tdls_mgmt) { return wpa_s->driver->send_tdls_mgmt(wpa_s->drv_priv, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, + buf, len); } return -1; } diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index ce11e98f..fc00b684 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -303,11 +303,11 @@ int wpa_supplicant_scard_init(struct wpa_supplicant *wpa_s, #ifdef PCSC_FUNCS int aka = 0, sim = 0; - if (ssid->eap.pcsc == NULL || wpa_s->scard != NULL || - wpa_s->conf->external_sim) + if ((ssid != NULL && ssid->eap.pcsc == NULL) || + wpa_s->scard != NULL || wpa_s->conf->external_sim) return 0; - if (ssid->eap.eap_methods == NULL) { + if (ssid == NULL || ssid->eap.eap_methods == NULL) { sim = 1; aka = 1; } else { @@ -1069,8 +1069,12 @@ int wpa_supplicant_connect(struct wpa_supplicant *wpa_s, wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_OVERLAP "PBC session overlap"); #ifdef CONFIG_P2P - if (wpas_p2p_notif_pbc_overlap(wpa_s) == 1) + if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT || + wpa_s->p2p_in_provisioning) { + eloop_register_timeout(0, 0, wpas_p2p_pbc_overlap_cb, + wpa_s, NULL); return -1; + } #endif /* CONFIG_P2P */ #ifdef CONFIG_WPS @@ -3278,6 +3282,12 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, wpa_dbg(wpa_s, MSG_DEBUG, "Interface was enabled"); if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { wpa_supplicant_update_mac_addr(wpa_s); + if (wpa_s->p2p_mgmt) { + wpa_supplicant_set_state(wpa_s, + WPA_DISCONNECTED); + break; + } + #ifdef CONFIG_AP if (!wpa_s->ap_iface) { wpa_supplicant_set_state(wpa_s, diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index 71163c39..3450ffea 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -260,8 +260,10 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, info_ids[num_info_ids++] = ANQP_IP_ADDR_TYPE_AVAILABILITY; if (all || cred_with_nai_realm(wpa_s)) info_ids[num_info_ids++] = ANQP_NAI_REALM; - if (all || cred_with_3gpp(wpa_s)) + if (all || cred_with_3gpp(wpa_s)) { info_ids[num_info_ids++] = ANQP_3GPP_CELLULAR_NETWORK; + wpa_supplicant_scard_init(wpa_s, NULL); + } if (all || cred_with_domain(wpa_s)) info_ids[num_info_ids++] = ANQP_DOMAIN_NAME; wpa_hexdump(MSG_DEBUG, "Interworking: ANQP Query info", @@ -1748,6 +1750,31 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) } +#ifdef PCSC_FUNCS +static int interworking_pcsc_read_imsi(struct wpa_supplicant *wpa_s) +{ + size_t len; + + if (wpa_s->imsi[0] && wpa_s->mnc_len) + return 0; + + len = sizeof(wpa_s->imsi) - 1; + if (scard_get_imsi(wpa_s->scard, wpa_s->imsi, &len)) { + scard_deinit(wpa_s->scard); + wpa_s->scard = NULL; + wpa_msg(wpa_s, MSG_ERROR, "Could not read IMSI"); + return -1; + } + wpa_s->imsi[len] = '\0'; + wpa_s->mnc_len = scard_get_mnc_len(wpa_s->scard); + wpa_printf(MSG_DEBUG, "SCARD: IMSI %s (MNC length %d)", + wpa_s->imsi, wpa_s->mnc_len); + + return 0; +} +#endif /* PCSC_FUNCS */ + + static struct wpa_cred * interworking_credentials_available_3gpp( struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, int *excluded) @@ -1786,8 +1813,9 @@ static struct wpa_cred * interworking_credentials_available_3gpp( size_t msin_len; #ifdef PCSC_FUNCS - if (cred->pcsc && wpa_s->conf->pcsc_reader && wpa_s->scard && - wpa_s->imsi[0]) { + if (cred->pcsc && wpa_s->scard) { + if (interworking_pcsc_read_imsi(wpa_s) < 0) + continue; imsi = wpa_s->imsi; mnc_len = wpa_s->mnc_len; goto compare; @@ -2043,13 +2071,14 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, int mnc_len = 0; if (cred->imsi) imsi = cred->imsi; -#ifdef CONFIG_PCSC - else if (cred->pcsc && wpa_s->conf->pcsc_reader && - wpa_s->scard && wpa_s->imsi[0]) { +#ifdef PCSC_FUNCS + else if (cred->pcsc && wpa_s->scard) { + if (interworking_pcsc_read_imsi(wpa_s) < 0) + return -1; imsi = wpa_s->imsi; mnc_len = wpa_s->mnc_len; } -#endif /* CONFIG_PCSC */ +#endif /* PCSC_FUNCS */ #ifdef CONFIG_EAP_PROXY else if (cred->pcsc && wpa_s->mnc_len > 0 && wpa_s->imsi[0]) { imsi = wpa_s->imsi; diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 5e36a673..303b7feb 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -6392,6 +6392,13 @@ int wpas_p2p_notif_pbc_overlap(struct wpa_supplicant *wpa_s) } +void wpas_p2p_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + wpas_p2p_notif_pbc_overlap(wpa_s); +} + + void wpas_p2p_update_channel_list(struct wpa_supplicant *wpa_s) { struct p2p_channels chan, cli_chan; diff --git a/wpa_supplicant/p2p_supplicant.h b/wpa_supplicant/p2p_supplicant.h index d3d36b1d..0bf3ca9b 100644 --- a/wpa_supplicant/p2p_supplicant.h +++ b/wpa_supplicant/p2p_supplicant.h @@ -158,6 +158,7 @@ int wpas_p2p_nfc_report_handover(struct wpa_supplicant *wpa_s, int init, const struct wpabuf *req, const struct wpabuf *sel, int forced_freq); int wpas_p2p_nfc_tag_enabled(struct wpa_supplicant *wpa_s, int enabled); +void wpas_p2p_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx); #ifdef CONFIG_P2P int wpas_p2p_4way_hs_failed(struct wpa_supplicant *wpa_s); diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index a860afb8..2538ba0c 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -477,8 +477,14 @@ void sme_authenticate(struct wpa_supplicant *wpa_s, } if (radio_work_pending(wpa_s, "sme-connect")) { - wpa_dbg(wpa_s, MSG_DEBUG, "SME: Reject sme_authenticate() call since pending work exist"); - return; + /* + * The previous sme-connect work might no longer be valid due to + * the fact that the BSS list was updated. In addition, it makes + * sense to adhere to the 'newer' decision. + */ + wpa_dbg(wpa_s, MSG_DEBUG, + "SME: Remove previous pending sme-connect"); + radio_remove_works(wpa_s, "sme-connect", 0); } cwork = os_zalloc(sizeof(*cwork)); diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index dce1c008..5c6f6252 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -2922,6 +2922,27 @@ static int wpa_set_disable_sgi(struct wpa_supplicant *wpa_s, } +static int wpa_set_disable_ldpc(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + /* Masking these out disables LDPC */ + u16 msk = host_to_le16(HT_CAP_INFO_LDPC_CODING_CAP); + + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_ldpc: %d", disabled); + + if (disabled) + htcaps->ht_capabilities_info &= ~msk; + else + htcaps->ht_capabilities_info |= msk; + + htcaps_mask->ht_capabilities_info |= msk; + + return 0; +} + + void wpa_supplicant_apply_ht_overrides( struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, struct wpa_driver_associate_params *params) @@ -2945,6 +2966,7 @@ void wpa_supplicant_apply_ht_overrides( wpa_set_ampdu_density(wpa_s, htcaps, htcaps_mask, ssid->ampdu_density); wpa_set_disable_ht40(wpa_s, htcaps, htcaps_mask, ssid->disable_ht40); wpa_set_disable_sgi(wpa_s, htcaps, htcaps_mask, ssid->disable_sgi); + wpa_set_disable_ldpc(wpa_s, htcaps, htcaps_mask, ssid->disable_ldpc); } #endif /* CONFIG_HT_OVERRIDES */ @@ -2957,6 +2979,10 @@ void wpa_supplicant_apply_vht_overrides( { struct ieee80211_vht_capabilities *vhtcaps; struct ieee80211_vht_capabilities *vhtcaps_mask; +#ifdef CONFIG_HT_OVERRIDES + int max_ampdu; + const u32 max_ampdu_mask = VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX; +#endif /* CONFIG_HT_OVERRIDES */ if (!ssid) return; @@ -2972,6 +2998,20 @@ void wpa_supplicant_apply_vht_overrides( vhtcaps->vht_capabilities_info = ssid->vht_capa; vhtcaps_mask->vht_capabilities_info = ssid->vht_capa_mask; +#ifdef CONFIG_HT_OVERRIDES + /* if max ampdu is <= 3, we have to make the HT cap the same */ + if (ssid->vht_capa_mask & max_ampdu_mask) { + max_ampdu = (ssid->vht_capa & max_ampdu_mask) >> + find_first_bit(max_ampdu_mask); + + max_ampdu = max_ampdu < 3 ? max_ampdu : 3; + wpa_set_ampdu_factor(wpa_s, + (void *) params->htcaps, + (void *) params->htcaps_mask, + max_ampdu); + } +#endif /* CONFIG_HT_OVERRIDES */ + #define OVERRIDE_MCS(i) \ if (ssid->vht_tx_mcs_nss_ ##i >= 0) { \ vhtcaps_mask->vht_supported_mcs_set.tx_map |= \ diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 03c6f5fe..3358250a 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -947,6 +947,10 @@ fast_reauth=1 # 0 = SGI enabled (if AP supports it) # 1 = SGI disabled # +# disable_ldpc: Whether LDPC should be disabled. +# 0 = LDPC enabled (if AP supports it) +# 1 = LDPC disabled +# # ht_mcs: Configure allowed MCS rates. # Parsed as an array of bytes, in base-16 (ascii-hex) # ht_mcs="" // Use all available (default) diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index e8a4b356..b2a330cf 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -558,12 +558,12 @@ static int wpa_supplicant_tdls_get_capa(void *ctx, int *tdls_supported, static int wpa_supplicant_send_tdls_mgmt(void *ctx, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, - size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { struct wpa_supplicant *wpa_s = ctx; return wpa_drv_send_tdls_mgmt(wpa_s, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, buf, len); } diff --git a/wpa_supplicant/wps_supplicant.c b/wpa_supplicant/wps_supplicant.c index b086c471..dfcc0693 100644 --- a/wpa_supplicant/wps_supplicant.c +++ b/wpa_supplicant/wps_supplicant.c @@ -510,15 +510,6 @@ static int wpa_supplicant_wps_cred(void *ctx, } -#ifdef CONFIG_P2P -static void wpas_wps_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx) -{ - struct wpa_supplicant *wpa_s = eloop_ctx; - wpas_p2p_notif_pbc_overlap(wpa_s); -} -#endif /* CONFIG_P2P */ - - static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s, struct wps_event_m2d *m2d) { @@ -537,7 +528,7 @@ static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s, * Notify P2P from eloop timeout to avoid issues with the * interface getting removed while processing a message. */ - eloop_register_timeout(0, 0, wpas_wps_pbc_overlap_cb, wpa_s, + eloop_register_timeout(0, 0, wpas_p2p_pbc_overlap_cb, wpa_s, NULL); } #endif /* CONFIG_P2P */ |
