diff --git a/client/dialclient_debug b/client/dialclient_debug new file mode 100755 index 0000000..5d900ce Binary files /dev/null and b/client/dialclient_debug differ diff --git a/client/makefile b/client/makefile index c35cb16..61bc528 100644 --- a/client/makefile +++ b/client/makefile @@ -6,6 +6,8 @@ CC=$(TARGET)g++ includes = $(wildcard *.h) OBJS := main.cpp DialServer.cpp DialDiscovery.cpp DialConformance.cpp DialClientInput.cpp +debug: dialclient_debug + # You may not need all these libraries. This example uses a build of curl that needs crypto, ssl, cares, and zlib dialclient: $(OBJS) ${includes} $(CC) -Wall -Werror -g $(OBJS) $(INCLUDES) $(LDFLAGS) -ldl -lpthread -lcurl -lz -lcrypto -lssl -o dialclient diff --git a/makefile b/makefile index e934e46..9d65da6 100644 --- a/makefile +++ b/makefile @@ -3,6 +3,8 @@ DIRS += server all: for dir in $(DIRS); do (make -C $$dir || exit 1) || exit 1; done +debug: + for dir in $(DIRS); do (make debug -C $$dir || exit 1) || exit 1; done clean: for dir in $(DIRS); do (make clean -C $$dir || exit 1) || exit 1; done diff --git a/server/dial_server.c b/server/dial_server.c index 87bc09b..8cb5ed9 100644 --- a/server/dial_server.c +++ b/server/dial_server.c @@ -492,24 +492,47 @@ static void handle_dial_data(struct mg_connection *conn, ds_unlock(ds); } - -static int host_matches(const char *origin_host, const char *candidate) { +/** + * Returns true if the origin is acceptable based on the candidate value. + * The candidate may accept any subdomain if its domain begins with *. and + * may require an exact port number match if it includes a colon to + * indicate a port number. + * + * This function assumes that the candidate value is well-formed, meaning + * it will not include invalid characters or a non-numeric port number. + * + * @param origin the origin header value, which must begin with https:// + * @param candidate the accepted origin whitelist value, which must begin + * with https:// + * @return true if accepted and false if not. + */ +static int host_matches(const char *origin, const char *candidate) { // Make sure there is something to compare. - if (!origin_host || !candidate) + if (!origin || !candidate) return 0; - // Make sure the candidate also begins with HTTPS. - if (strncmp(candidate, gHttpsProto, strlen(gHttpsProto)) != 0) + // Make sure the origin and candidate both begin with HTTPS. + const size_t https_len= strlen(gHttpsProto); + if (strncmp(origin, gHttpsProto, https_len) != 0 || + strncmp(candidate, gHttpsProto, https_len) != 0) + { return 0; + } // For the rest of the check, we only care about the hostname and optional // port number. - const char *host = candidate + strlen(gHttpsProto); - - // If the host contains a port number (indicated by a colon) require an - // exact match. + const char * origin_host = origin + https_len; + const char * host = candidate + https_len; + + // Set the initial lengths for comparison. + size_t origin_len = strlen(origin_host); + size_t host_len = strlen(host); + + // Look for port numbers. const char * origin_colon = strrchr(origin_host, ':'); const char * host_colon = strrchr(host, ':'); + + // If the host contains a port number (indicated by a colon)... if (host_colon != NULL) { // If the host port number is 443, then accept the origin host if it // does not have any port number under the assumption we already @@ -518,27 +541,59 @@ static int host_matches(const char *origin_host, const char *candidate) { strncmp(host_colon, ":443", 4) == 0 && origin_colon == NULL) { - // Strip off the host port number and require an exact match. - size_t host_len = host_colon - host; - if (host_len == 0) - return 0; - return strncmp(origin_host, host, host_len) == 0; + // We will ignore the host port number of 443. + host_len = host_colon - host; } - - // Other port numbers must match exactly. - if (strlen(origin_host) != strlen(host)) - return 0; - return strncmp(origin_host, host, strlen(host)) == 0; + + // Other port numbers must match exactly. So leave the host length + // untouched. } - - // Otherwise strip off any port number in the origin host, and require an - // exact match. - size_t origin_len = (origin_colon == NULL) ? strlen(origin_host) : origin_colon - origin_host; - if (origin_len == 0) + + // Otherwise ignore any port number in the origin. + else if (origin_colon != NULL) { + origin_len = origin_colon - origin_host; + } + + // At this point, the origin length excludes any port number if the host + // does not specify one, and the host length excludes its port number if + // it was 443 and there is no origin port number. + // + // If either length is zero then fail. + if (origin_len == 0 || host_len == 0) return 0; - if (origin_len != strlen(host)) - return 0; - return strncmp(origin_host, host, origin_len) == 0; + + // Check to see if the host permits subdomains. + const char * wildcard = "*."; + const int acceptSubdomain = (host_len > strlen(wildcard)) + ? strncmp(host, wildcard, strlen(wildcard)) == 0 + : 0; + + // If the host accepts subdomains, verify that the origin ends with the + // portion of the host that occurs after the subdomain wildcard. + if (acceptSubdomain) { + // The origin must be at least as long as the host. + if (origin_len < host_len) + return 0; + + // Skip the subdomain of the origin, which should equate to the + // wildcard of the host. Likewise skip the wildcard of the host. + const char * origin_domain = strchr(origin_host, '.'); + const char * host_domain = host + 1; + if (!origin_domain || !host_domain) + return 0; + + // Remove from comparison the characters we skipped. + origin_len -= (origin_domain - origin_host); + host_len -= 1; + + // The remainder must be an exact match. + return (origin_len == host_len && + strncmp(origin_domain, host_domain, origin_len) == 0); + } + + // Otherwise the host and origin must be an exact match. + return (origin_len == host_len && + strncmp(origin_host, host, origin_len) == 0); } static int origin_matches(const char *origin, const char *candidate) { @@ -559,7 +614,6 @@ static int is_uri_in_list(const char *origin, const char *list) { return 0; int isHttps = (strncmp(origin, gHttpsProto, strlen(gHttpsProto)) == 0); - const char * origin_host = (isHttps) ? origin + strlen(gHttpsProto) : NULL; const char * scanPointer = list; const char * spacePointer; @@ -587,7 +641,7 @@ static int is_uri_in_list(const char *origin, const char *list) { // If the URI begins with https://, perform a host comparison because // any port numbers must be handled specially. Otherwise require an // exact match. - if ((isHttps && host_matches(origin_host, candidate)) || + if ((isHttps && host_matches(origin, candidate)) || (!isHttps && origin_matches(origin, candidate))) { free(candidate); candidate = NULL; @@ -596,7 +650,7 @@ static int is_uri_in_list(const char *origin, const char *list) { scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character } free(candidate); candidate = NULL; - return ((isHttps && host_matches(origin_host, scanPointer)) || + return ((isHttps && host_matches(origin, scanPointer)) || (!isHttps && origin_matches(origin, scanPointer))); } diff --git a/server/main.c b/server/main.c index 8e21624..69ba304 100644 --- a/server/main.c +++ b/server/main.c @@ -288,8 +288,13 @@ void runDial(void) struct DIALAppCallbacks cb_yt = {youtube_start, youtube_hide, youtube_stop, youtube_status}; struct DIALAppCallbacks cb_system = {system_start, system_hide, NULL, system_status}; +#if defined DEBUG if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, "https://netflix.com https://www.netflix.com https://port.netflix.com:123") == -1 || - DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://www.youtube.com https://port.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube") == -1 || + DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://www.youtube.com https://*.youtube.com:443 https://port.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube") == -1 || +#else + if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, "https://netflix.com https://www.netflix.com") == -1 || + DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://*.youtube.com package:com.google.android.youtube package:com.google.ios.youtube") == -1 || +#endif DIAL_register_app(ds, "system", &cb_system, NULL, 1, "") == -1) { printf("Unable to register DIAL applications.\n"); diff --git a/server/makefile b/server/makefile index 62492b8..cca618c 100644 --- a/server/makefile +++ b/server/makefile @@ -12,16 +12,18 @@ HEADERS := $(wildcard *.h) # $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o $(CC) -Wall -g -fPIC -std=gnu99 $(CFLAGS) -c $*.c -o $*.o -all: dialserver test +all: dialserver +debug: dialserver_debug test nf_callbacks_lib: nf_callbacks.o # $(CC) -Wall -Werror -g nf_callbacks.o -o libnfCallbacks.so --shared $(CC) -Wall -Werror -Wl,-undefined -Wl,dynamic_lookup -g nf_callbacks.o -o libnfCallbacks.so --shared - dialserver: nf_callbacks_lib $(OBJS) $(CC) -Wall -Werror -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver +dialserver_debug: nf_callbacks_lib $(OBJS) + $(CC) -DDEBUG -Wall -Werror -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver dialserver_with_ASAN: nf_callbacks_lib $(OBJS) $(CC) -Wall -Werror -fsanitize=address -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver_with_ASAN diff --git a/server/tests/test_cors.sh b/server/tests/test_cors.sh index cc55a40..f51304c 100755 --- a/server/tests/test_cors.sh +++ b/server/tests/test_cors.sh @@ -21,7 +21,7 @@ then fi done -origins="https://www.youtube.com https://youtube.com https://port.youtube.com:123 https://www.youtube.com:80 https://www.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube" +origins="https://www.youtube.com https://music.youtube.com https://youtube.com https://port.youtube.com:123 https://www.youtube.com:80 https://www.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube" for origin in $origins; do curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted" curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted" @@ -46,7 +46,7 @@ then fi done -origins="http://www.youtube-a.com http://www.youtube.com4 http://a-youtube.com https://ww.youtube-a.com http://www4.youtube.com https://port.youtube.com:1234 http://1.youtube.com https://www4.youtube.com https://www.youtube.com4 https://a-youtube.com http://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com ftp://this.is.not.fine packagecom.google.android.youtube package:com.google.android.utube" +origins="http://www.youtube-a.com http://www.youtube.com4 https://.youtube.com http://a-youtube.com https://ww.youtube-a.com http://www4.youtube.com https://port.youtube.com:1234 http://1.youtube.com https://www.youtube.com4 https://a-youtube.com http://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com ftp://this.is.not.fine packagecom.google.android.youtube package:com.google.android.utube" for origin in $origins; do curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected" curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"