diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/integration/exploid-keyring-with-dupe-keys.pub | bin | 0 -> 3986 bytes | |||
-rw-r--r-- | test/integration/exploid-keyring-with-dupe-subkeys.pub | bin | 0 -> 2016 bytes | |||
-rw-r--r-- | test/integration/framework | 20 | ||||
-rwxr-xr-x | test/integration/skip-aptwebserver | 25 | ||||
-rwxr-xr-x | test/integration/skip-bug-602412-dequote-redirect | 38 | ||||
-rwxr-xr-x | test/integration/test-apt-key-net-update | 95 | ||||
-rwxr-xr-x | test/integration/test-bug-602412-dequote-redirect | 29 | ||||
-rwxr-xr-x | test/integration/test-bug-666772-multiarch-arch-all-build-deps | 118 | ||||
-rwxr-xr-x | test/integration/test-ubuntu-bug-346386-apt-get-update-paywall | 47 | ||||
-rw-r--r-- | test/interactive-helper/aptwebserver.cc | 513 | ||||
-rw-r--r-- | test/interactive-helper/makefile | 7 |
11 files changed, 849 insertions, 43 deletions
diff --git a/test/integration/exploid-keyring-with-dupe-keys.pub b/test/integration/exploid-keyring-with-dupe-keys.pub Binary files differnew file mode 100644 index 000000000..642952a40 --- /dev/null +++ b/test/integration/exploid-keyring-with-dupe-keys.pub diff --git a/test/integration/exploid-keyring-with-dupe-subkeys.pub b/test/integration/exploid-keyring-with-dupe-subkeys.pub Binary files differnew file mode 100644 index 000000000..02d4e6ee8 --- /dev/null +++ b/test/integration/exploid-keyring-with-dupe-subkeys.pub diff --git a/test/integration/framework b/test/integration/framework index 1c4872c8e..11f1c7be2 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -690,20 +690,30 @@ signreleasefiles() { } changetowebserver() { - if which weborf > /dev/null; then - weborf -xb aptarchive/ 2>&1 > /dev/null & + if [ -n "$1" ] && ! test -x ${BUILDDIRECTORY}/aptwebserver; then + msgdie 'Need the aptwebserver when passing arguments' + fi + + local LOG='/dev/null' + if test -x ${BUILDDIRECTORY}/aptwebserver; then + cd aptarchive + LD_LIBRARY_PATH=${BUILDDIRECTORY} ${BUILDDIRECTORY}/aptwebserver $@ 2> $LOG > $LOG & + addtrap "kill $!;" + cd - > /dev/null + elif which weborf > /dev/null; then + weborf -xb aptarchive/ 2> $LOG > $LOG & addtrap "kill $!;" elif which gatling > /dev/null; then cd aptarchive - gatling -p 8080 -F -S 2>&1 > /dev/null & + gatling -p 8080 -F -S 2> $LOG > $LOG & addtrap "kill $!;" cd - > /dev/null elif which lighttpd > /dev/null; then echo "server.document-root = \"$(readlink -f ./aptarchive)\" server.port = 8080 server.stat-cache-engine = \"disable\"" > lighttpd.conf - lighttpd -t -f lighttpd.conf >/dev/null || msgdie 'Can not change to webserver: our lighttpd config is invalid' - lighttpd -D -f lighttpd.conf 2>/dev/null >/dev/null & + lighttpd -t -f lighttpd.conf 2> $LOG > $LOG || msgdie 'Can not change to webserver: our lighttpd config is invalid' + lighttpd -D -f lighttpd.conf 2> $LOG > $LOG & addtrap "kill $!;" else msgdie 'You have to install weborf or lighttpd first' diff --git a/test/integration/skip-aptwebserver b/test/integration/skip-aptwebserver new file mode 100755 index 000000000..0622941ce --- /dev/null +++ b/test/integration/skip-aptwebserver @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework + +setupenvironment +configarchitecture 'amd64' + +buildsimplenativepackage 'apt' 'all' '1.0' 'stable' + +setupaptarchive +changetowebserver + +rm -rf rootdir/var/lib/apt/lists +aptget update -qq +testequal 'Hit http://localhost stable InRelease +Hit http://localhost stable/main Sources +Hit http://localhost stable/main amd64 Packages +Hit http://localhost stable/main Translation-en +Reading package lists...' aptget update + +mv rootdir/var/lib/apt/lists/localhost* rootdir/var/lib/apt/lists/partial +aptget update + diff --git a/test/integration/skip-bug-602412-dequote-redirect b/test/integration/skip-bug-602412-dequote-redirect deleted file mode 100755 index 689b671ce..000000000 --- a/test/integration/skip-bug-602412-dequote-redirect +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -set -e - -TESTDIR=$(readlink -f $(dirname $0)) -. $TESTDIR/framework -setupenvironment -configarchitecture 'i386' - -if ! which lighttpd > /dev/null; then - msgdie 'You need lighttpd for this testcase, sorry…' - exit 1 -fi - -buildsimplenativepackage 'unrelated' 'all' '0.5~squeeze1' 'unstable' - -setupaptarchive - -echo "server.modules = ( \"mod_redirect\" ) -server.document-root = \"$(readlink -f ./aptarchive)\" -server.port = 8080 -server.stat-cache-engine = \"disable\" -url.redirect = ( \"^/pool/(.*)$\" => \"/newpool/\$1\", - \"^/dists/(.*)$\" => \"/newdists/\$1\" )" > lighttpd.conf - -mv aptarchive/pool aptarchive/newpool -mv aptarchive/dists aptarchive/newdists - -lighttpd -t -f lighttpd.conf >/dev/null || msgdie 'Can not change to webserver: our lighttpd config is invalid' -lighttpd -D -f lighttpd.conf 2>/dev/null >/dev/null & -addtrap "kill $!;" - -APTARCHIVE="file://$(readlink -f ./aptarchive)" -for LIST in $(find rootdir/etc/apt/sources.list.d/ -name 'apt-test-*.list'); do - sed -i $LIST -e "s#$APTARCHIVE#http://localhost:8080/#" -done - -aptget update || msgdie 'apt-get update failed' -aptget install unrelated --download-only || msgdie 'downloading package failed' diff --git a/test/integration/test-apt-key-net-update b/test/integration/test-apt-key-net-update new file mode 100755 index 000000000..d5205836f --- /dev/null +++ b/test/integration/test-apt-key-net-update @@ -0,0 +1,95 @@ +#!/bin/sh +set -e + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework + +setupenvironment +configarchitecture "i386" + +# mock +requires_root() { + return 0 +} + +# extract net_update() and import it +func=$( sed -n -e '/^add_keys_with_verify_against_master_keyring/,/^}/p' ${BUILDDIRECTORY}/apt-key ) +eval "$func" + +mkdir -p ./etc/apt +TRUSTEDFILE=./etc/apt/trusted.gpg +mkdir -p ./var/lib/apt/keyrings +TMP_KEYRING=./var/lib/apt/keyrings/maybe-import-keyring.gpg +GPG_CMD="gpg --ignore-time-conflict --no-options --no-default-keyring" +GPG="$GPG_CMD --keyring $TRUSTEDFILE" +MASTER_KEYRING=/usr/share/keyrings/ubuntu-master-keyring.gpg + + +msgtest "add_keys_with_verify_against_master_keyring" +if [ ! -e $MASTER_KEYRING ]; then + echo -n "No $MASTER_KEYRING found" + msgskip + exit 0 +fi + +# test bad keyring and ensure its not added (LP: #857472) +ADD_KEYRING=./keys/exploid-keyring-with-dupe-keys.pub +if add_keys_with_verify_against_master_keyring $ADD_KEYRING $MASTER_KEYRING; then + msgfail +else + msgpass +fi + +# ensure the keyring is still empty +gpg_out=$($GPG --list-keys) +msgtest "Test if keyring is empty" +if [ -n "" ]; then + msgfail +else + msgpass +fi + + +# test another possible attack vector using subkeys (LP: #1013128) +msgtest "add_keys_with_verify_against_master_keyring with subkey attack" +ADD_KEYRING=./keys/exploid-keyring-with-dupe-subkeys.pub +if add_keys_with_verify_against_master_keyring $ADD_KEYRING $MASTER_KEYRING; then + msgfail +else + msgpass +fi + +# ensure the keyring is still empty +gpg_out=$($GPG --list-keys) +msgtest "Test if keyring is empty" +if [ -n "" ]; then + msgfail +else + msgpass +fi + + +# test good keyring and ensure we get no errors +ADD_KEYRING=/usr/share/keyrings/ubuntu-archive-keyring.gpg +if add_keys_with_verify_against_master_keyring $ADD_KEYRING $MASTER_KEYRING; then + msgpass +else + msgfail +fi + +testequal './etc/apt/trusted.gpg +--------------------- +pub 1024D/437D05B5 2004-09-12 +uid Ubuntu Archive Automatic Signing Key <ftpmaster@ubuntu.com> +sub 2048g/79164387 2004-09-12 + +pub 1024D/FBB75451 2004-12-30 +uid Ubuntu CD Image Automatic Signing Key <cdimage@ubuntu.com> + +pub 4096R/C0B21F32 2012-05-11 +uid Ubuntu Archive Automatic Signing Key (2012) <ftpmaster@ubuntu.com> + +pub 4096R/EFE21092 2012-05-11 +uid Ubuntu CD Image Automatic Signing Key (2012) <cdimage@ubuntu.com> +' $GPG --list-keys + diff --git a/test/integration/test-bug-602412-dequote-redirect b/test/integration/test-bug-602412-dequote-redirect new file mode 100755 index 000000000..f1e67c6d8 --- /dev/null +++ b/test/integration/test-bug-602412-dequote-redirect @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework +setupenvironment +configarchitecture 'amd64' + +buildsimplenativepackage 'unrelated' 'all' '0.5~squeeze1' 'unstable' + +setupaptarchive +changetowebserver -o aptwebserver::redirect::replace::/pool/=/newpool/ \ + -o aptwebserver::redirect::replace::/dists/=/newdists/ + +mv aptarchive/pool aptarchive/newpool +mv aptarchive/dists aptarchive/newdists + +msgtest 'Test redirection works in' 'apt-get update' +aptget update -qq && msgpass || msgfail + +# check that I-M-S header is kept in redirections +testequal 'Hit http://localhost unstable InRelease +Hit http://localhost unstable/main Sources +Hit http://localhost unstable/main amd64 Packages +Hit http://localhost unstable/main Translation-en +Reading package lists...' aptget update + +msgtest 'Test redirection works in' 'package download' +aptget install unrelated --download-only -qq && msgpass || msgfail diff --git a/test/integration/test-bug-666772-multiarch-arch-all-build-deps b/test/integration/test-bug-666772-multiarch-arch-all-build-deps new file mode 100755 index 000000000..cfae1fef3 --- /dev/null +++ b/test/integration/test-bug-666772-multiarch-arch-all-build-deps @@ -0,0 +1,118 @@ +#!/bin/sh +set -e + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework +setupenvironment +configarchitecture 'amd64' 'armhf' + +insertinstalledpackage 'build-essential' 'all' '11.5' + +insertpackage 'unstable' 'doxygen' 'all' '1.0' 'Depends: language-support, language-tool' +insertpackage 'unstable' 'libc6' 'amd64,armhf' '1.0' 'Multi-Arch: same' +insertpackage 'unstable' 'libc6-dev' 'amd64,armhf' '1.0' 'Depends: libc6 +Multi-Arch: same' +insertpackage 'unstable' 'language-support' 'amd64,armhf' '1.0' 'Multi-Arch: foreign' +insertpackage 'unstable' 'language-tool' 'amd64,armhf' '1.0' + +insertsource 'unstable' 'apt' 'any' '0.8.15' 'Build-Depends: doxygen, libc6-dev' + +setupaptarchive + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-support language-tool libc6 libc6-dev +0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded. +Inst language-support (1.0 unstable [amd64]) +Inst language-tool (1.0 unstable [amd64]) +Inst doxygen (1.0 unstable [all]) +Inst libc6 (1.0 unstable [amd64]) +Inst libc6-dev (1.0 unstable [amd64]) +Conf language-support (1.0 unstable [amd64]) +Conf language-tool (1.0 unstable [amd64]) +Conf doxygen (1.0 unstable [all]) +Conf libc6 (1.0 unstable [amd64]) +Conf libc6-dev (1.0 unstable [amd64])' aptget build-dep apt -s + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-support language-tool libc6:armhf libc6-dev:armhf +0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded. +Inst language-support (1.0 unstable [amd64]) +Inst language-tool (1.0 unstable [amd64]) +Inst doxygen (1.0 unstable [all]) +Inst libc6:armhf (1.0 unstable [armhf]) +Inst libc6-dev:armhf (1.0 unstable [armhf]) +Conf language-support (1.0 unstable [amd64]) +Conf language-tool (1.0 unstable [amd64]) +Conf doxygen (1.0 unstable [all]) +Conf libc6:armhf (1.0 unstable [armhf]) +Conf libc6-dev:armhf (1.0 unstable [armhf])' aptget build-dep apt -s -a armhf + +configarchitecture 'armhf' 'amd64' + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-support language-tool libc6 libc6-dev +0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded. +Inst language-support (1.0 unstable [armhf]) +Inst language-tool (1.0 unstable [armhf]) +Inst doxygen (1.0 unstable [all]) +Inst libc6 (1.0 unstable [armhf]) +Inst libc6-dev (1.0 unstable [armhf]) +Conf language-support (1.0 unstable [armhf]) +Conf language-tool (1.0 unstable [armhf]) +Conf doxygen (1.0 unstable [all]) +Conf libc6 (1.0 unstable [armhf]) +Conf libc6-dev (1.0 unstable [armhf])' aptget build-dep apt -s + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-support language-tool libc6:amd64 libc6-dev:amd64 +0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded. +Inst language-support (1.0 unstable [armhf]) +Inst language-tool (1.0 unstable [armhf]) +Inst doxygen (1.0 unstable [all]) +Inst libc6:amd64 (1.0 unstable [amd64]) +Inst libc6-dev:amd64 (1.0 unstable [amd64]) +Conf language-support (1.0 unstable [armhf]) +Conf language-tool (1.0 unstable [armhf]) +Conf doxygen (1.0 unstable [all]) +Conf libc6:amd64 (1.0 unstable [amd64]) +Conf libc6-dev:amd64 (1.0 unstable [amd64])' aptget build-dep apt -s -a amd64 + +configarchitecture 'amd64' 'armhf' + +insertinstalledpackage 'language-support' 'armhf' '0.5' 'Multi-Arch: foreign' + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-tool libc6 libc6-dev +0 upgraded, 4 newly installed, 0 to remove and 1 not upgraded. +Inst language-tool (1.0 unstable [amd64]) +Inst doxygen (1.0 unstable [all]) +Inst libc6 (1.0 unstable [amd64]) +Inst libc6-dev (1.0 unstable [amd64]) +Conf language-tool (1.0 unstable [amd64]) +Conf doxygen (1.0 unstable [all]) +Conf libc6 (1.0 unstable [amd64]) +Conf libc6-dev (1.0 unstable [amd64])' aptget build-dep apt -s + +testequal 'Reading package lists... +Building dependency tree... +The following NEW packages will be installed: + doxygen language-tool libc6:armhf libc6-dev:armhf +0 upgraded, 4 newly installed, 0 to remove and 1 not upgraded. +Inst language-tool (1.0 unstable [amd64]) +Inst doxygen (1.0 unstable [all]) +Inst libc6:armhf (1.0 unstable [armhf]) +Inst libc6-dev:armhf (1.0 unstable [armhf]) +Conf language-tool (1.0 unstable [amd64]) +Conf doxygen (1.0 unstable [all]) +Conf libc6:armhf (1.0 unstable [armhf]) +Conf libc6-dev:armhf (1.0 unstable [armhf])' aptget build-dep apt -s -a armhf diff --git a/test/integration/test-ubuntu-bug-346386-apt-get-update-paywall b/test/integration/test-ubuntu-bug-346386-apt-get-update-paywall new file mode 100755 index 000000000..25cccf067 --- /dev/null +++ b/test/integration/test-ubuntu-bug-346386-apt-get-update-paywall @@ -0,0 +1,47 @@ +#!/bin/sh +set -e + +ensure_n_canary_strings_in_dir() { + DIR=$1 + CANARY_STRING=$2 + EXPECTED_N=$3 + + msgtest "Testing for $EXPECTED_N canary strings '$CANARY_STRING' in in" "$DIR" + + N=$(grep "$CANARY_STRING" $DIR/* 2>/dev/null |wc -l ) + if [ "$N" = "$EXPECTED_N" ]; then + msgpass + return 0 + else + msgfail "Expected $EXPECTED_N canaries, got $N" + return 1 + fi +} + +TESTDIR=$(readlink -f $(dirname $0)) +. $TESTDIR/framework + +setupenvironment +configarchitecture 'native' + +insertpackage 'unstable' 'unrelated' 'all' '1.0' 'stable' + +setupaptarchive +changetowebserver --simulate-paywall + +rm -rf rootdir/var/lib/apt/lists +msgtest 'excpected failure of' 'apt-get update' +aptget update -qq 2>/dev/null && msgfail || msgpass + +ensure_n_canary_strings_in_dir rootdir/var/lib/apt/lists/ 'ni ni ni' 0 +testequal 'partial' ls rootdir/var/lib/apt/lists/ + +# again, this time with pre-existing files valid data +for f in Release Release.gpg main_binary-amd64_Packages stable_main_source_Sources; do + echo "canary" > rootdir/var/lib/apt/lists/localhost:8080_dists_stable_${f} +done + +# this will fail, the important part is that the canaries remain +msgtest 'excpected failure of' 'apt-get update' +aptget update -qq 2>/dev/null && msgfail || msgpass +ensure_n_canary_strings_in_dir rootdir/var/lib/apt/lists/ 'canary' 4 diff --git a/test/interactive-helper/aptwebserver.cc b/test/interactive-helper/aptwebserver.cc new file mode 100644 index 000000000..ff60d64a3 --- /dev/null +++ b/test/interactive-helper/aptwebserver.cc @@ -0,0 +1,513 @@ +#include <config.h> + +#include <apt-pkg/strutl.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/error.h> +#include <apt-pkg/cmndline.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/init.h> + +#include <vector> +#include <string> +#include <list> +#include <sstream> + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <stdlib.h> +#include <dirent.h> +#include <signal.h> + +char const * const httpcodeToStr(int const httpcode) { /*{{{*/ + switch (httpcode) { + // Informational 1xx + case 100: return "100 Continue"; + case 101: return "101 Switching Protocols"; + // Successful 2xx + case 200: return "200 OK"; + case 201: return "201 Created"; + case 202: return "202 Accepted"; + case 203: return "203 Non-Authoritative Information"; + case 204: return "204 No Content"; + case 205: return "205 Reset Content"; + case 206: return "206 Partial Content"; + // Redirections 3xx + case 300: return "300 Multiple Choices"; + case 301: return "301 Moved Permanently"; + case 302: return "302 Found"; + case 303: return "303 See Other"; + case 304: return "304 Not Modified"; + case 305: return "304 Use Proxy"; + case 307: return "307 Temporary Redirect"; + // Client errors 4xx + case 400: return "400 Bad Request"; + case 401: return "401 Unauthorized"; + case 402: return "402 Payment Required"; + case 403: return "403 Forbidden"; + case 404: return "404 Not Found"; + case 405: return "405 Method Not Allowed"; + case 406: return "406 Not Acceptable"; + case 407: return "407 Proxy Authentication Required"; + case 408: return "408 Request Time-out"; + case 409: return "409 Conflict"; + case 410: return "410 Gone"; + case 411: return "411 Length Required"; + case 412: return "412 Precondition Failed"; + case 413: return "413 Request Entity Too Large"; + case 414: return "414 Request-URI Too Large"; + case 415: return "415 Unsupported Media Type"; + case 416: return "416 Requested range not satisfiable"; + case 417: return "417 Expectation Failed"; + // Server error 5xx + case 500: return "500 Internal Server Error"; + case 501: return "501 Not Implemented"; + case 502: return "502 Bad Gateway"; + case 503: return "503 Service Unavailable"; + case 504: return "504 Gateway Time-out"; + case 505: return "505 HTTP Version not supported"; + } + return NULL; +} + /*}}}*/ +void addFileHeaders(std::list<std::string> &headers, FileFd &data) { /*{{{*/ + std::ostringstream contentlength; + contentlength << "Content-Length: " << data.FileSize(); + headers.push_back(contentlength.str()); + + std::string lastmodified("Last-Modified: "); + lastmodified.append(TimeRFC1123(data.ModificationTime())); + headers.push_back(lastmodified); + + std::string const fileext = flExtension(data.Name()); + if (fileext.empty() == false && fileext != data.Name()) { + std::string confcontenttype("aptwebserver::ContentType::"); + confcontenttype.append(fileext); + std::string const contenttype = _config->Find(confcontenttype); + if (contenttype.empty() == false) { + std::string header("Content-Type: "); + header.append(contenttype); + headers.push_back(header); + } + } +} + /*}}}*/ +void addDataHeaders(std::list<std::string> &headers, std::string &data) {/*{{{*/ + std::ostringstream contentlength; + contentlength << "Content-Length: " << data.size(); + headers.push_back(contentlength.str()); +} + /*}}}*/ +bool sendHead(int const client, int const httpcode, std::list<std::string> &headers) { /*{{{*/ + std::string response("HTTP/1.1 "); + response.append(httpcodeToStr(httpcode)); + headers.push_front(response); + + headers.push_back("Server: APT webserver"); + + std::string date("Date: "); + date.append(TimeRFC1123(time(NULL))); + headers.push_back(date); + + headers.push_back("Accept-Ranges: bytes"); + + std::clog << ">>> RESPONSE >>>" << std::endl; + bool Success = true; + for (std::list<std::string>::const_iterator h = headers.begin(); + Success == true && h != headers.end(); ++h) { + Success &= FileFd::Write(client, h->c_str(), h->size()); + if (Success == true) + Success &= FileFd::Write(client, "\r\n", 2); + std::clog << *h << std::endl; + } + if (Success == true) + Success &= FileFd::Write(client, "\r\n", 2); + std::clog << "<<<<<<<<<<<<<<<<" << std::endl; + return Success; +} + /*}}}*/ +bool sendFile(int const client, FileFd &data) { /*{{{*/ + bool Success = true; + char buffer[500]; + unsigned long long actual = 0; + while ((Success &= data.Read(buffer, sizeof(buffer), &actual)) == true) { + if (actual == 0) + break; + if (Success == true) + Success &= FileFd::Write(client, buffer, actual); + } + if (Success == true) + Success &= FileFd::Write(client, "\r\n", 2); + return Success; +} + /*}}}*/ +bool sendData(int const client, std::string const &data) { /*{{{*/ + bool Success = true; + Success &= FileFd::Write(client, data.c_str(), data.size()); + if (Success == true) + Success &= FileFd::Write(client, "\r\n", 2); + return Success; +} + /*}}}*/ +void sendError(int const client, int const httpcode, std::string const &request, bool content, std::string const &error = "") { /*{{{*/ + std::list<std::string> headers; + std::string response("<html><head><title>"); + response.append(httpcodeToStr(httpcode)).append("</title></head>"); + response.append("<body><h1>").append(httpcodeToStr(httpcode)).append("</h1>"); + if (error.empty() == false) + response.append("<p><em>Error</em>: ").append(error).append("</p>"); + response.append("This error is a result of the request: <pre>"); + response.append(request).append("</pre></body></html>"); + addDataHeaders(headers, response); + sendHead(client, httpcode, headers); + if (content == true) + sendData(client, response); +} + /*}}}*/ +void sendRedirect(int const client, int const httpcode, std::string const &uri, std::string const &request, bool content) { /*{{{*/ + std::list<std::string> headers; + std::string response("<html><head><title>"); + response.append(httpcodeToStr(httpcode)).append("</title></head>"); + response.append("<body><h1>").append(httpcodeToStr(httpcode)).append("</h1"); + response.append("<p>You should be redirected to <em>").append(uri).append("</em></p>"); + response.append("This page is a result of the request: <pre>"); + response.append(request).append("</pre></body></html>"); + addDataHeaders(headers, response); + std::string location("Location: "); + if (strncmp(uri.c_str(), "http://", 7) != 0) + location.append("http://").append(LookupTag(request, "Host")).append("/").append(uri); + else + location.append(uri); + headers.push_back(location); + sendHead(client, httpcode, headers); + if (content == true) + sendData(client, response); +} + /*}}}*/ +// sendDirectoryLisiting /*{{{*/ +int filter_hidden_files(const struct dirent *a) { + if (a->d_name[0] == '.') + return 0; +#ifdef _DIRENT_HAVE_D_TYPE + // if we have the d_type check that only files and dirs will be included + if (a->d_type != DT_UNKNOWN && + a->d_type != DT_REG && + a->d_type != DT_LNK && // this includes links to regular files + a->d_type != DT_DIR) + return 0; +#endif + return 1; +} +int grouped_alpha_case_sort(const struct dirent **a, const struct dirent **b) { +#ifdef _DIRENT_HAVE_D_TYPE + if ((*a)->d_type == DT_DIR && (*b)->d_type == DT_DIR); + else if ((*a)->d_type == DT_DIR && (*b)->d_type == DT_REG) + return -1; + else if ((*b)->d_type == DT_DIR && (*a)->d_type == DT_REG) + return 1; + else +#endif + { + struct stat f_prop; //File's property + stat((*a)->d_name, &f_prop); + int const amode = f_prop.st_mode; + stat((*b)->d_name, &f_prop); + int const bmode = f_prop.st_mode; + if (S_ISDIR(amode) && S_ISDIR(bmode)); + else if (S_ISDIR(amode)) + return -1; + else if (S_ISDIR(bmode)) + return 1; + } + return strcasecmp((*a)->d_name, (*b)->d_name); +} +void sendDirectoryListing(int const client, std::string const &dir, std::string const &request, bool content) { + std::list<std::string> headers; + std::ostringstream listing; + + struct dirent **namelist; + int const counter = scandir(dir.c_str(), &namelist, filter_hidden_files, grouped_alpha_case_sort); + if (counter == -1) { + sendError(client, 500, request, content); + return; + } + + listing << "<html><head><title>Index of " << dir << "</title>" + << "<style type=\"text/css\"><!-- td {padding: 0.02em 0.5em 0.02em 0.5em;}" + << "tr:nth-child(even){background-color:#dfdfdf;}" + << "h1, td:nth-child(3){text-align:center;}" + << "table {margin-left:auto;margin-right:auto;} --></style>" + << "</head>" << std::endl + << "<body><h1>Index of " << dir << "</h1>" << std::endl + << "<table><tr><th>#</th><th>Name</th><th>Size</th><th>Last-Modified</th></tr>" << std::endl; + if (dir != ".") + listing << "<tr><td>d</td><td><a href=\"..\">Parent Directory</a></td><td>-</td><td>-</td></tr>"; + for (int i = 0; i < counter; ++i) { + struct stat fs; + std::string filename(dir); + filename.append("/").append(namelist[i]->d_name); + stat(filename.c_str(), &fs); + if (S_ISDIR(fs.st_mode)) { + listing << "<tr><td>d</td>" + << "<td><a href=\"" << namelist[i]->d_name << "/\">" << namelist[i]->d_name << "</a></td>" + << "<td>-</td>"; + } else { + listing << "<tr><td>f</td>" + << "<td><a href=\"" << namelist[i]->d_name << "\">" << namelist[i]->d_name << "</a></td>" + << "<td>" << SizeToStr(fs.st_size) << "B</td>"; + } + listing << "<td>" << TimeRFC1123(fs.st_mtime) << "</td></tr>" << std::endl; + } + listing << "</table></body></html>" << std::endl; + + std::string response(listing.str()); + addDataHeaders(headers, response); + sendHead(client, 200, headers); + if (content == true) + sendData(client, response); +} + /*}}}*/ +bool parseFirstLine(int const client, std::string const &request, std::string &filename, bool &sendContent, bool &closeConnection) { /*{{{*/ + if (strncmp(request.c_str(), "HEAD ", 5) == 0) + sendContent = false; + if (strncmp(request.c_str(), "GET ", 4) != 0) + { + sendError(client, 501, request, true); + return false; + } + + size_t const lineend = request.find('\n'); + size_t filestart = request.find(' '); + for (; request[filestart] == ' '; ++filestart); + size_t fileend = request.rfind(' ', lineend); + if (lineend == std::string::npos || filestart == std::string::npos || + fileend == std::string::npos || filestart == fileend) { + sendError(client, 500, request, sendContent, "Filename can't be extracted"); + return false; + } + + size_t httpstart = fileend; + for (; request[httpstart] == ' '; ++httpstart); + if (strncmp(request.c_str() + httpstart, "HTTP/1.1\r", 9) == 0) + closeConnection = strcasecmp(LookupTag(request, "Connection", "Keep-Alive").c_str(), "Keep-Alive") != 0; + else if (strncmp(request.c_str() + httpstart, "HTTP/1.0\r", 9) == 0) + closeConnection = strcasecmp(LookupTag(request, "Connection", "Keep-Alive").c_str(), "close") == 0; + else { + sendError(client, 500, request, sendContent, "Not an HTTP/1.{0,1} request"); + return false; + } + + filename = request.substr(filestart, fileend - filestart); + if (filename.find(' ') != std::string::npos) { + sendError(client, 500, request, sendContent, "Filename contains an unencoded space"); + return false; + } + filename = DeQuoteString(filename); + + // this is not a secure server, but at least prevent the obvious … + if (filename.empty() == true || filename[0] != '/' || + strncmp(filename.c_str(), "//", 2) == 0 || + filename.find_first_of("\r\n\t\f\v") != std::string::npos || + filename.find("/../") != std::string::npos) { + sendError(client, 400, request, sendContent, "Filename contains illegal character (sequence)"); + return false; + } + + // nuke the first character which is a / as we assured above + filename.erase(0, 1); + if (filename.empty() == true) + filename = "."; + return true; +} + /*}}}*/ +int main(int const argc, const char * argv[]) +{ + CommandLine::Args Args[] = { + {0, "simulate-paywall", "aptwebserver::Simulate-Paywall", + CommandLine::Boolean}, + {0, "port", "aptwebserver::port", CommandLine::HasArg}, + {'c',"config-file",0,CommandLine::ConfigFile}, + {'o',"option",0,CommandLine::ArbItem}, + {0,0,0,0} + }; + + CommandLine CmdL(Args, _config); + if(CmdL.Parse(argc,argv) == false) { + _error->DumpErrors(); + exit(1); + } + + // create socket, bind and listen to it {{{ + // ignore SIGPIPE, this can happen on write() if the socket closes connection + signal(SIGPIPE, SIG_IGN); + int sock = socket(AF_INET6, SOCK_STREAM, 0); + if(sock < 0 ) { + _error->Errno("aptwerbserver", "Couldn't create socket"); + _error->DumpErrors(std::cerr); + return 1; + } + + // get the port + int const port = _config->FindI("aptwebserver::port", 8080); + bool const simulate_broken_server = _config->FindB("aptwebserver::Simulate-Paywall", false); + + // ensure that we accept all connections: v4 or v6 + int const iponly = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &iponly, sizeof(iponly)); + // to not linger to an address + int const enable = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + + struct sockaddr_in6 locAddr; + memset(&locAddr, 0, sizeof(locAddr)); + locAddr.sin6_family = AF_INET6; + locAddr.sin6_port = htons(port); + locAddr.sin6_addr = in6addr_any; + + if (bind(sock, (struct sockaddr*) &locAddr, sizeof(locAddr)) < 0) { + _error->Errno("aptwerbserver", "Couldn't bind"); + _error->DumpErrors(std::cerr); + return 2; + } + + if (simulate_broken_server) { + std::clog << "Simulating a broken web server that return nonsense " + "for all querries" << std::endl; + } else { + std::clog << "Serving ANY file on port: " << port << std::endl; + } + + listen(sock, 1); + /*}}}*/ + + std::vector<std::string> messages; + int client; + while ((client = accept(sock, NULL, NULL)) != -1) { + std::clog << "ACCEPT client " << client + << " on socket " << sock << std::endl; + + while (ReadMessages(client, messages)) { + bool closeConnection = false; + for (std::vector<std::string>::const_iterator m = messages.begin(); + m != messages.end() && closeConnection == false; ++m) { + std::clog << ">>> REQUEST >>>>" << std::endl << *m + << std::endl << "<<<<<<<<<<<<<<<<" << std::endl; + std::list<std::string> headers; + std::string filename; + bool sendContent = true; + if (parseFirstLine(client, *m, filename, sendContent, closeConnection) == false) + continue; + + std::string host = LookupTag(*m, "Host", ""); + if (host.empty() == true) { + // RFC 2616 §14.23 requires Host + sendError(client, 400, *m, sendContent, "Host header is required"); + continue; + } + + if (simulate_broken_server == true) { + std::string data("ni ni ni\n"); + addDataHeaders(headers, data); + sendHead(client, 200, headers); + sendData(client, data); + } + else if (RealFileExists(filename) == true) { + FileFd data(filename, FileFd::ReadOnly); + std::string condition = LookupTag(*m, "If-Modified-Since", ""); + if (condition.empty() == false) { + time_t cache; + if (RFC1123StrToTime(condition.c_str(), cache) == true && + cache >= data.ModificationTime()) { + sendHead(client, 304, headers); + continue; + } + } + condition = LookupTag(*m, "If-Range", ""); + bool ignoreRange = false; + if (condition.empty() == false) { + time_t cache; + if (RFC1123StrToTime(condition.c_str(), cache) == false || + cache < data.ModificationTime()) + ignoreRange = true; + } + condition = LookupTag(*m, "Range", ""); + if (ignoreRange == false && condition.empty() == false && + strncmp(condition.c_str(), "bytes=", 6) == 0) { + size_t end = condition.find(','); + // FIXME: support multiple byte-ranges + if (end == std::string::npos) { + size_t start = 6; + unsigned long long filestart = strtoull(condition.c_str() + start, NULL, 10); + // FIXME: no fileend support + size_t dash = condition.find('-') + 1; + unsigned long long fileend = strtoull(condition.c_str() + dash, NULL, 10); + unsigned long long filesize = data.FileSize(); + if (fileend == 0 || fileend == filesize) { + if (filesize > filestart) { + data.Skip(filestart); + std::ostringstream contentlength; + contentlength << "Content-Length: " << (filesize - filestart); + headers.push_back(contentlength.str()); + std::ostringstream contentrange; + contentrange << "Content-Range: bytes " << filestart << "-" + << filesize - 1 << "/" << filesize; + headers.push_back(contentrange.str()); + sendHead(client, 206, headers); + if (sendContent == true) + sendFile(client, data); + continue; + } else { + headers.push_back("Content-Length: 0"); + std::ostringstream contentrange; + contentrange << "Content-Range: bytes 0-0/" << filesize; + headers.push_back(contentrange.str()); + sendHead(client, 416, headers); + continue; + } + } + } + } + + addFileHeaders(headers, data); + sendHead(client, 200, headers); + if (sendContent == true) + sendFile(client, data); + } + else if (DirectoryExists(filename) == true) { + if (filename == "." || filename[filename.length()-1] == '/') + sendDirectoryListing(client, filename, *m, sendContent); + else + sendRedirect(client, 301, filename.append("/"), *m, sendContent); + } + else + { + ::Configuration::Item const *Replaces = _config->Tree("aptwebserver::redirect::replace"); + if (Replaces != NULL) { + std::string redirect = "/" + filename; + for (::Configuration::Item *I = Replaces->Child; I != NULL; I = I->Next) + redirect = SubstVar(redirect, I->Tag, I->Value); + redirect.erase(0,1); + if (redirect != filename) { + sendRedirect(client, 301, redirect, *m, sendContent); + continue; + } + } + sendError(client, 404, *m, sendContent); + } + } + _error->DumpErrors(std::cerr); + messages.clear(); + if (closeConnection == true) + break; + } + + std::clog << "CLOSE client " << client + << " on socket " << sock << std::endl; + close(client); + } + return 0; +} diff --git a/test/interactive-helper/makefile b/test/interactive-helper/makefile index 10d1e44ec..fee94cd77 100644 --- a/test/interactive-helper/makefile +++ b/test/interactive-helper/makefile @@ -37,3 +37,10 @@ include $(PROGRAM_H) #SLIBS = -lapt-pkg -lrpm #SOURCE = rpmver.cc #include $(PROGRAM_H) + +# very simple webserver for APT testing +PROGRAM=aptwebserver +SLIBS = -lapt-pkg +LIB_MAKES = apt-pkg/makefile +SOURCE = aptwebserver.cc +include $(PROGRAM_H) |