Apache redirects based on symlinks - apache

In one app, some public image resources are symlinked to alternative names for historic reasons. For example, we might have
a.png
b.png -> a.png
Google's PageSpeed Insight that identical content should be served from a consistent URL applies to assets like this as well as content. Rather than reorganising the assets we have in place, I would like to have Apache perform an external redirect from b.png to a.png.
With mod_rewrite I can make a RewriteCond to narrow in on symbolic links:
'-l' (is symbolic link)
Treats the TestString as a pathname and tests whether or not it exists, and is a symbolic link.
But how can I get the expanded path of the symlink? I need it partly to ensure that the target is in web scope, and partly to perform the redirect.

(first post here)
I've been looking for a solution to a similar problem. It's possible, but there is no easy answer. I had to mix info from multiple Google searches to make it work.
First of all, a note about symlinks (wiki):
Symbolic links are automatically resolved by the file system. Any
software program, upon accessing a symbolic link, will see the target
instead, whether the program is aware of symbolic links or not.
So, unless the apache devs add specific options to deal with symlinks (like the -l you mentionned), we have to rely on another mean to properly resolve them.
mod_rewrite has the RewriteMap configuration option. With it, you can use different types of external resources to perform the rewriting, such as text files or DB queries. It can also call external programs, such as ... a symlink resolver for example :)
So, here we go. Run all the following commands as root and adapt to your system settings (this is for Debian).
First, if we use the RewriteMap option, we also need the RewriteLock option to prevent race conditions. The RewriteLock option can't be in a <VirtualHost> or <Directory> context. It has to be in the global context, so I added it in a new rewrite.conf and reloaded the module.
Run the following:
lock=/var/lock/apache2/rewrite_lock
touch $lock
chown www-data:www-data $lock
echo "RewriteLock $lock" >> /etc/apache2/mods-available/rewrite.conf
a2dismod rewrite
a2enmod rewrite
Next, create /usr/local/bin/resolve-symlink and add the following code:
#!/bin/sh
while read line
do
echo `readlink "$line"`
done
Make it executable and test it as apache:
chmod +x /usr/local/bin/resolve-symlink
su - www-data
/usr/local/bin/resolve-symlink
The script should wait for your input, then either return a blank line or the target of the given symlink. Use CTRL+C to exit. Example (> is STDIN, < is STDOUT):
> test
<
> /bin/sh
< bash
> /bin/bash
<
test doesn't exist, so a blank line is returned. /bin/sh is a symlink and the script resolved it to bash. Finally, /bin/bash is a file and not a link, thus another blank line.
Now, in your <VirtualHost> configuration, add the following lines:
RewriteEngine On
RewriteMap symlink prg:/usr/local/bin/resolve-symlink
RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 9
RewriteMap can't be in a <Directory> context, and won't be active unless RewriteEngine is also set to on in the <VirtualHost> context, even if you later set it to on in a <Directory> contest!!! Pay attention to all those peculiarities and read your log files carefully or you may lose a few hours banging your head on the wall wondering why it says "map lookup FAILED".
Finally, the rewriting, in whatever context you prefer:
RewriteCond %{REQUEST_FILENAME} -l
RewriteCond ${symlink:%{REQUEST_FILENAME}} ^(.+)$
RewriteRule $ ${REQUEST_SCHEME}://${HTTP_HOST}/%1 [R,L]
First line detects symlinks, second one performs the resolution and checks that the results is not empty, third line rewrites the URL (%1 is the result of the mapping) and sends a 302 redirection to the browser. You may need the END flag instead of the L flag.
Now restart apache...
service apache2 restart
... test, check your log files, tweak, rinse and repeat...
When your are done, don't forget to remove the RewriteLog and RewriteLogLevel directives from your configuration because they slow apache down a lot.
Important note. In my case, the symlinks all points to files within the same folder, so the URL rewriting is a little easier. If your symlinks point to sub-directories, or even somewhere else on the filesystem, you will have to modify the script and the configuration to account for it, but even then it might not work as expected, as apache won't always be able to figure out the correct URL to send to the browser. For example, if you have a symlink to, let say, /usr/share/apache2/icons/apache_pb.png, apache might redirect to http://example.com/usr/share/apache2/icons/apache_pb.png which, of course, does not exists...
Also, I would have added a few informative links but I'm limited to 2... Anyway, happy debugging!

I've got more information about this topic. It's been running for quite some time on my side and I made the following amendments to the file /usr/local/bin/resolve-symlinks:
#!/bin/bash
BASE=_ALL_YOUR_BASE_ARE_BELONG_TO_US_
BASE_LEN=${#BASE}
while read FILE
do
# $FILE must be in $BASE
if [[ $BASE != ${FILE:0:$BASE_LEN} ]]
then
echo
continue
fi
REL_FILE=${FILE:$BASE_LEN}
# echo $REL_FILE
# $LINK must also be in $BASE
LINK=$(readlink -f $FILE)
if [[ $BASE != ${LINK:0:$BASE_LEN} ]]
then
echo
continue
fi
REL_LINK=${LINK:$BASE_LEN}
# echo $REL_LINK
# $REL_FILE and $REL_LINK must be different
if [[ $(basename $REL_FILE) == $(basename $REL_LINK) ]]
then
echo
continue;
fi
# success!
echo $REL_LINK
done
Of course, replace _ALL_YOUR_BASE_ARE_BELONG_TO_US_ with your favorite path.
Now it should make sure both the requested file and the link target are in the same directory.

Related

Windows 10 - Apache 2.4.53 + PHP 8.0.19 not loading php modules [duplicate]

I have found that:
When I type the following on terminal:
php -i | grep php.ini
I get the output:
The Loaded Configuration file is # /etc/php5/cli/php.ini
However, from phpinfo(), I get to see:
The loaded ini file is # /etc/php5/apache2/php.ini
Which one of these is working right now? How is it possible to have two php.ini files ?
Depends on where you are running PHP from. If you run it from command line, it uses the cli/php.ini and apache2/php.ini when run through apache.
You are executing phpinfo() through the browser, hence you get /etc/php5/apache2/php.ini as the answer. Running php -r "phpinfo();" | grep "Loaded Configuration" from the terminal should output the CLI ini. Same function, context changes.
The advantage of this system is obviously to allow different configurations depending on the context. For a simplified example, you might want to have safe_mode on in apache but it's unnecessary in CLI mode.
Your .ini paths are actually quite unusual. Normally, the default .ini is just php.ini and CLI .ini is called php-cli.ini and they reside in the same folder.
I'm no expert on the subject but this should be the basic idea. If anyone has any corrections, I'd be happy to hear them.

symlink on bash on windows : apache tries to open a .htaccess that doesn't exist

i try to work on a php project that uses symlinks (it was developped on linux).
i'm on windows, i use bash on windows aka bash on ubuntu on windows aka windows subsystem for linux as a development server.
when apache tries to reach a file within the symlinks it fails.
e.g. when i try to open /var/www/website/symlink/index.html it returns a 403 :
[core:error] [pid 742] AH00554: Access to file /var/www/website/symlink/.htaccess denied by server: not a regular file
i don't know why but it seems apache tries to open a .htaccess file that does not exist, and of course fails at it.
(the symlink points out of the scope of the virtualhost root)
everything is 777.
i can successfully access /var/www/website/symlink/index.html from the command line.
i can successfully access the index.html file when i remove the symlink and copy its target directory instead.
i've tried to delete symlinks and recreate them, it doesn't change anything.
i've tried to create /var/www/website/symlink/.htaccess but it doesn't change anything.
does it sound like a bug or a bad apache configuration or something else ?
this issue was related to this bug : https://github.com/Microsoft/BashOnWindows/issues/650
i got everything to work by removing symlinks and recreate them without trailing slash.
it seems that in a symlink with a trailing slash every directories and every files exist (just try to cd dsfkjhgfkdshjajklhdaf e.g.)
this explains that apache was finding a non existent .htaccess file and was desperately trying to read it.
i wonder when this bug will get fixed though...

How do I get apache to follow symlinks to windows drive on WSL

I'm trying to make apache run on Linux Subsystem for Windows.
So I just installed apache and changed DocumentRoot from "/var/www/html" to "/var/www" in /etc/apache2/sites-available/000-default.conf.
This has exactly the effect I wanted it to have, namely to list all folders inside when browsing to "localhost".
My projects are located on my Windows drive in "C:/Projekte", so I created a symlink like so:
sudo ln -s /mnt/c/Projekte /var/www/projects
Now my problem is, when I browse to localhost and click on projects it runs into a loop trying to call an index.html, so the url before it breaks looks like this:
http://localhost/projects/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/index.html/
I would want to see the directory listing of my folders instead.
I know about the Options Indexes and FollowSymLinks which are both set, but there seems to be something else I have to set for symlinks to work as I expect.
If I create a folder instead in "/var/www" it works fine.
Does anybody know how to set this up correctly?
By accident I found out, that my issue here is related to this issue on BashOnWindows. Problem is that I used tab to autocomplete the path which automatically added a trailing slash to the end of the path. So the actual command I used to create the symlink was
sudo ln -s /mnt/c/Projekte/ /var/www/projects
Creating the symlink without trailing slash, just like in my question it works just fine.

Vim: Sudo Edit Breaks Apache Syntax Highlighting

Sudo Edit (sudo -e) allows unprivileged users to edit files securely. To achieve this, it makes a temporary copy of a file to edit and then copies it over when editing is done.
When I'm editing an apache file (eg, /etc/apache2/sites-enabled/mysite.com), sudoedit vim can't figure out that it should use Apache syntax highlighting, so I have to manually :set syntax=apache. I suspect that Vim's rule for syntax highlighting relies on the full path of the file, and since sudoedit changes the file to something like /var/tmp/mysiteRANDOMCHARS.com, it loses that path information.
Is there any way for me to automatically tell Vim that it should use apache syntax highlighting?
Thanks!
This sounds like one of the rare use-cases for the vi modeline feature:
In your .vimrc:
set modeline
And in your Apache config file, somewhere at the top or the bottom:
# vi: syntax=apache
See /usr/share/vim/vim7x/filetype.vim for how Vim determines that a file is an apache file.
In my config, using $ vim or $ sudo -e makes no difference as the .com extension is treated as dcl anyway.
I don't see a smart and solid way to workaround that beside overriding the .com autocommand in your ~/.vimrc:
au BufNewFile,BufRead *.com set ft=apache

Declaring global variable with php.ini

Is it possible to keep variables in php.ini file. Like that we do with the web.config in .net. I like to keep a flag type variable in the php.ini and use it to different projects.
It's not possible to set user-level variables within a plain php.ini file (or the .htaccess equivilents). There are some PECL modules that do allow that, such as hidef (http://pecl.php.net/package/hidef) - though these would need to be installed on every installation you use.
Including (or pre-including) a file with auto_prepend_file is quite possible - though that would be on every PHP request.
What is frequently done is setting an environment variable as part of the webserver process, which can be read from PHP. In Apache this is quite easy, with the SetEnv module.
SetEnv PRODUCTION_SERVER 1
And accessing it in PHP:
if ($_ENV['PRODUCTION_SERVER']) {...} // or getenv('PRODUCTION_SERVER')
Have you looked at get_cfg_var()?
I needed to do something similar, and this was able to do it for me.
Nope.
You could use the auto_prepend_file directive to automatically include a file that said, although as it uses the include_path, you'd need to specify the full path.
However, it's probably more transparent just to explicitly include/require the relevant file.
One technique that I have found useful for passing a limited number of global variables to a bootstrap script is to take advantage of the SetEnv directive in an .htaccess file. The advantage is that the variable you set will be made available to any script in that directory, plus any scripts in child directories under it.
You could use a SetEnv varibale with the location of a configuration file, such as:
in .htaccess:
SetEnv init_path /home/hendepher/TestApp/init/init.php
In your .php scipt:
<?php
if(!getenv('init_path')) throw new Exception('Must set init_path in .htaccess');
require_once getenv('init_path');
.
.
.
?>
If you have a test directory that requires different initialization o global variables, simply add another .htaccess file in your test directory:
SetEnv init_path /home/hendepher/TestApp/init/testing_init.php
Doing it this way, as opposed to using the 'auto_prepend_file' directive, is that your global configuration script is not run by all the php applications on your server: some may not need it.
The accepted answere also worked for me, with one change.
I didn't test this on earlier versions, but in my environment (php 5.4.22) this doesn't show up in $_ENV, but rather in $_SERVER.
In my .htacess file:
SetEnv PRODUCTION_SERVER 0.
My php code:
$production="PRODUCTION";
if (!isset($_SERVER['PRODUCTION_SERVER']) || $_SERVER['PRODUCTION_SERVER'] != 1){
$production="DEVELOPMENT";
}
I don't think that's a good place to store variables. php.ini is for storing configuration for PHP itself not your applications. You should consider putting the shared variables into a .inc file and including that instead.
Have you considered hidef?
Allow definition of user defined constants in simple ini files,
which are then processed like internal constants, without any
of the usual performance penalties.
Complementing #Ascherer answer, use get_cfg_var() to save custom variables in custom php.ini (variable created by you, not an official PHP ini directive). For example:
In php.ini: custom_variable = "abcde"
In any php script: get_cfg_var('custom_variable') returns abcde
I use this in in a small project in local dev. As I run the local server via php -S localhost:8000 -c php.ini (not running an Apache server locally), it's a good option to call some configuration constants. In production, these constants are set in .htaccess.