|  | #!/usr/bin/perl | 
|  |  | 
|  | # (C) Andrey Belov | 
|  |  | 
|  | # Tests for disable_symlinks directive. | 
|  |  | 
|  | ############################################################################### | 
|  |  | 
|  | use warnings; | 
|  | use strict; | 
|  |  | 
|  | use Test::More; | 
|  | use POSIX; | 
|  | use Cwd qw/ realpath /; | 
|  |  | 
|  | BEGIN { use FindBin; chdir($FindBin::Bin); } | 
|  |  | 
|  | use lib 'lib'; | 
|  | use Test::Nginx; | 
|  |  | 
|  | ############################################################################### | 
|  |  | 
|  | select STDERR; $| = 1; | 
|  | select STDOUT; $| = 1; | 
|  |  | 
|  | my $t = Test::Nginx->new()->has(qw/http rewrite symlink/) | 
|  | ->write_file_expand('nginx.conf', <<'EOF'); | 
|  |  | 
|  | %%TEST_GLOBALS%% | 
|  |  | 
|  | daemon off; | 
|  |  | 
|  | events { | 
|  | } | 
|  |  | 
|  | http { | 
|  | %%TEST_GLOBALS_HTTP%% | 
|  |  | 
|  | server { | 
|  | listen       127.0.0.1:8080; | 
|  | server_name  s1; | 
|  |  | 
|  | location /on/ { | 
|  | disable_symlinks on; | 
|  | } | 
|  |  | 
|  | location /not_owner/ { | 
|  | disable_symlinks if_not_owner; | 
|  | } | 
|  |  | 
|  | location /try_on/ { | 
|  | disable_symlinks on; | 
|  | try_files $uri $uri.html =404; | 
|  | } | 
|  |  | 
|  | location /try_not_owner/ { | 
|  | disable_symlinks if_not_owner; | 
|  | try_files $uri $uri.txt =404; | 
|  | } | 
|  |  | 
|  | location /if_on/ { | 
|  | disable_symlinks on; | 
|  | if (-f $request_filename) { | 
|  | return 204; | 
|  | } | 
|  | } | 
|  |  | 
|  | location /if_not_owner/ { | 
|  | disable_symlinks if_not_owner; | 
|  | if (-f $request_filename) { | 
|  | return 204; | 
|  | } | 
|  | } | 
|  |  | 
|  | location /complex/1/ { | 
|  | disable_symlinks on; | 
|  | alias %%TESTDIR%%/./cached/../; | 
|  | } | 
|  |  | 
|  | location /complex/2/ { | 
|  | disable_symlinks on; | 
|  | alias %%TESTDIR%%//./cached/..//; | 
|  | } | 
|  |  | 
|  | location /complex/3/ { | 
|  | disable_symlinks on; | 
|  | alias ///%%TESTDIR%%//./cached/..//; | 
|  | } | 
|  |  | 
|  | location ~ (.+/)tail$ { | 
|  | disable_symlinks on; | 
|  | alias %%TESTDIR%%/$1; | 
|  | } | 
|  |  | 
|  | location ~ (.+/)tailowner$ { | 
|  | disable_symlinks if_not_owner; | 
|  | alias %%TESTDIR%%/$1; | 
|  | } | 
|  |  | 
|  | location ~ (.+/)tailoff$ { | 
|  | disable_symlinks off; | 
|  | alias %%TESTDIR%%/$1; | 
|  | } | 
|  |  | 
|  | location /dir { | 
|  | disable_symlinks on; | 
|  | try_files $uri/ =404; | 
|  | } | 
|  |  | 
|  | location /from { | 
|  | disable_symlinks on from=$document_root; | 
|  |  | 
|  | location /from/wo_slash { | 
|  | alias %%TESTDIR%%/dirlink; | 
|  | } | 
|  | location /from/with_slash/ { | 
|  | alias %%TESTDIR%%/dirlink/; | 
|  | } | 
|  | location ~ ^/from/exact/(.+)$ { | 
|  | alias %%TESTDIR%%/$1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | server { | 
|  | listen       127.0.0.1:8080; | 
|  | server_name  s2; | 
|  |  | 
|  | open_file_cache max=16 inactive=60s; | 
|  | open_file_cache_valid 30s; | 
|  | open_file_cache_min_uses 1; | 
|  | open_file_cache_errors on; | 
|  |  | 
|  | location /cached-off/ { | 
|  | disable_symlinks off; | 
|  | alias %%TESTDIR%%/cached/; | 
|  | } | 
|  |  | 
|  | location /cached-on/ { | 
|  | disable_symlinks on; | 
|  | alias %%TESTDIR%%/cached/; | 
|  | } | 
|  |  | 
|  | location /cached-if-not-owner/ { | 
|  | disable_symlinks if_not_owner; | 
|  | alias %%TESTDIR%%/cached/; | 
|  | } | 
|  |  | 
|  | location / { | 
|  | disable_symlinks off; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | EOF | 
|  |  | 
|  | my $uid = getuid(); | 
|  | my ($extfile) = grep { -f && !-l && $uid != (stat())[4] } | 
|  | ('/etc/resolv.conf', '/etc/protocols', '/etc/host.conf'); | 
|  |  | 
|  | plan(skip_all => 'no external file found') | 
|  | if !defined $extfile; | 
|  |  | 
|  | $t->try_run('no disable_symlinks')->plan(28); | 
|  |  | 
|  | my $d = $t->testdir(); | 
|  |  | 
|  | mkdir("$d/on"); | 
|  | mkdir("$d/not_owner"); | 
|  | mkdir("$d/try_on"); | 
|  | mkdir("$d/try_not_owner"); | 
|  | mkdir("$d/if_on"); | 
|  | mkdir("$d/if_not_owner"); | 
|  | mkdir("$d/cached"); | 
|  |  | 
|  | $t->write_file("empty.html", ""); | 
|  | symlink("empty.html", "$d/link"); | 
|  | symlink($extfile, "$d/link2"); | 
|  |  | 
|  | $t->write_file("on/empty.html", ""); | 
|  | symlink("empty.html", "$d/on/link"); | 
|  | symlink($extfile, "$d/on/link2"); | 
|  |  | 
|  | $t->write_file("not_owner/empty.html", ""); | 
|  | symlink("empty.html", "$d/not_owner/link"); | 
|  | symlink($extfile, "$d/not_owner/link2"); | 
|  |  | 
|  | $t->write_file("try_on/try.html", "LOCAL TRY"); | 
|  | symlink($extfile, "$d/try_on/try"); | 
|  |  | 
|  | $t->write_file("try_not_owner/try.html", "LOCAL TRY"); | 
|  | symlink($extfile, "$d/try_not_owner/try"); | 
|  | symlink("try.html", "$d/try_not_owner/try.txt"); | 
|  |  | 
|  | $t->write_file("if_on/empty.html", ""); | 
|  | symlink("empty.html", "$d/if_on/link"); | 
|  | symlink($extfile, "$d/if_on/link2"); | 
|  |  | 
|  | $t->write_file("if_not_owner/empty.html", ""); | 
|  | symlink("empty.html", "$d/if_not_owner/link"); | 
|  | symlink($extfile, "$d/if_not_owner/link2"); | 
|  |  | 
|  | mkdir("$d/dir"); | 
|  | $t->write_file("dir/empty.html", ""); | 
|  | symlink("dir", "$d/dirlink"); | 
|  |  | 
|  | symlink($extfile, "$d/cached/link"); | 
|  |  | 
|  | ############################################################################### | 
|  |  | 
|  | SKIP: { | 
|  | skip 'cannot test under symlink', 25 if $d ne realpath($d) or $^O eq 'netbsd'; | 
|  |  | 
|  | like(http_get_host('s1', '/link'), qr!200 OK!, 'static (off, same uid)'); | 
|  | like(http_get_host('s1', '/link2'), qr!200 OK!, 'static (off, other uid)'); | 
|  |  | 
|  | like(http_get_host('s1', '/on/link'), qr!403 Forbidden!, | 
|  | 'static (on, same uid)'); | 
|  | like(http_get_host('s1', '/on/link2'), qr!403 Forbidden!, | 
|  | 'static (on, other uid)'); | 
|  |  | 
|  | like(http_get_host('s1', '/not_owner/link'), qr!200 OK!, | 
|  | 'static (if_not_owner, same uid)'); | 
|  | like(http_get_host('s1', '/not_owner/link2'), qr!403 Forbidden!, | 
|  | 'static (if_not_owner, other uid)'); | 
|  |  | 
|  | like(http_get_host('s1', '/try_on/try'), qr/LOCAL TRY/, | 
|  | 'try_files (on)'); | 
|  | like(http_get_host('s1', '/try_not_owner/try'), qr/LOCAL TRY/, | 
|  | 'try_files (if_not_owner)'); | 
|  |  | 
|  | like(http_get_host('s1', '/if_on/link'), qr!403 Forbidden!, | 
|  | 'if (on, same uid)'); | 
|  | like(http_get_host('s1', '/if_on/link2'), qr!403 Forbidden!, | 
|  | 'if (on, other uid)'); | 
|  |  | 
|  | like(http_get_host('s1', '/if_not_owner/link'), qr!204 No Content!, | 
|  | 'if (if_not_owner, same uid)'); | 
|  | like(http_get_host('s1', '/if_not_owner/link2'), qr!403 Forbidden!, | 
|  | 'if (if_not_owner, other uid)'); | 
|  |  | 
|  | like(http_get_host('s2', '/cached-off/link'), qr!200 OK!, | 
|  | 'open_file_cache (pass 1)'); | 
|  | like(http_get_host('s2', '/cached-on/link'), qr!403 Forbidden!, | 
|  | 'open_file_cache (pass 2)'); | 
|  | like(http_get_host('s2', '/cached-off/link'), qr!200 OK!, | 
|  | 'open_file_cache (pass 3)'); | 
|  | like(http_get_host('s2', '/cached-if-not-owner/link'), qr!403 Forbidden!, | 
|  | 'open_file_cache (pass 4)'); | 
|  | like(http_get_host('s2', '/cached-off/link'), qr!200 OK!, | 
|  | 'open_file_cache (pass 5)'); | 
|  |  | 
|  | like(http_get('/complex/1/empty.html'), qr!200 OK!, 'complex root 1'); | 
|  | like(http_get('/complex/2/empty.html'), qr!200 OK!, 'complex root 2'); | 
|  | like(http_get('/complex/3/empty.html'), qr!200 OK!, 'complex root 3'); | 
|  |  | 
|  | # workaround for freebsd 8: we use O_EXEC instead of O_SEARCH (since there | 
|  | # is no O_SEARCH), and O_DIRECTORY does nothing.  setting the 'x' bit | 
|  | # tests to pass as openat() will correctly fail with ENOTDIR | 
|  |  | 
|  | chmod(0700, "$d/link"); | 
|  | my $rc = $^O eq 'darwin' ? 200 : 404; | 
|  |  | 
|  | like(http_get('/link/tail'), qr!40[34] !, 'file with trailing /, on'); | 
|  | like(http_get('/link/tailowner'), qr!404 !, 'file with trailing /, owner'); | 
|  | like(http_get('/link/tailoff'), qr!$rc !, 'file with trailing /, off'); | 
|  |  | 
|  | like(http_get('/dirlink'), qr!404 !, 'directory without /'); | 
|  | like(http_get('/dirlink/'), qr!404 !, 'directory with trailing /'); | 
|  |  | 
|  | } # SKIP: cannot test under symlink | 
|  |  | 
|  | like(http_get('/from/wo_slash/empty.html'), qr!200 OK!, '"from=" without /'); | 
|  | like(http_get('/from/with_slash/empty.html'), qr!200 OK!, '"from=" with /'); | 
|  | like(http_get('/from/exact/link'), qr!200 OK!, '"from=" exact match'); | 
|  |  | 
|  | ############################################################################### | 
|  |  | 
|  | sub http_get_host { | 
|  | my ($host, $url) = @_; | 
|  | return http(<<EOF); | 
|  | GET $url HTTP/1.0 | 
|  | Host: $host | 
|  |  | 
|  | EOF | 
|  | } | 
|  |  | 
|  | ############################################################################### |