User VHosting

Posted by on August 30, 2006

So, previously, I detailed how to do web virtual hosting to directories on the file system. This is all well and good, but I want to have users pidgeon holed into their own directories, plus I want users own websites to be stored under their own directories in a similar format to that used in the previous article (eg. /home/user/web/example.org/subdomain)

The problem lies with mapping domain names (example.org) onto user accounts (Alice? Bob?). This is where Apache and mod_rewrite come to the rescue again. On Wintermute I’ve installed Apache 2, but this recipe will work just as well with Apache 1.3.

First, we create a file with domain names to username mappings:
/etc/apache2/map.hosted

wintermute:/etc/apache2# cat map.hosted
xeno-phon.co.uk: jimsin
starfallonline.co.uk: stillborn
ryanroberts.co.uk: stillborn
witchhunter.co.uk: stillborn
flushedphoenix.co.uk: flushedphoenix
shawree.co.uk: shawree
theballswinger.co.uk: the_ball_swinger
the-hearse.com: hobgoth

The pattern is “website.com: username” with one per line.
Each of these usernames must also exist in the Xoops database (or as system users), but I’ll be covering the links to that DB in a later article.

Coming up with that file was the easy bit. The next bit was the major pain in the hole. I searched for aaaages to try and work out how to do this in php ( my php is significantly better than my Perl), but all the examples I could find were written in Perl. So, here are some keywords for search engines: how to write a mod_rewrite map in php not perl. php apache rewritemap.


#!/usr/bin/php
"")
{
$url["hostedUser"] = $uname;
}
else
{
$host = explode(".",$url["host"]);
$url["path"] = "/". $host[0] . $url["path"];
unset($host[0]);
$url["host"] = implode(".",$host);
if ($url["host"] <> "")
checkUser($url);
}

}

do
{
$in = trim(fgets(STDIN,8096));
if (strlen($in) >0)
{
//Log request
# fputs($log, "$in\n");
unset($url["hostedUser"]);
if (substr($in,0,1) <> "/")
{
$in = "http://$in";
$url = parse_url($in);
$path = $url["path"];
$url["path"] = "";
checkUser($url);
}
//If we find an owner
if(isset($url["hostedUser"]))
{
//This bit is slightly legacy before I moved the hosting files about a bit. Kept for posterity mainly.
$out = "/home/" . $url["hostedUser"] ."/web/" . $url["hostedUser"] . ".elizium.net/" . $url["host"] . $url["path"] . $path;
if (file_exists("/home/" . $url["hostedUser"] ."/web/" . $url["host"] . $url["path"] . $path))
{
$out = "/home/" . $url["hostedUser"] ."/web/" . $url["host"] . $url["path"] . $path;
}
}
//Otherwise just return this file as 99% chance its some automated script looking for exploitable webapps.
else
$out= "/var/www/monkey.txt";

//Log resulting path
# fputs($log, "$out\n");
fputs(STDOUT, "$out\n");

}
//Just keep doing it forever.
} while (true);
?>

The only improvement I may make to that is to get the users home directory info as opposed to assuming its under /home.

Now we just need put that script somewhere, make it executable by the Apache user and tell Apache to use it. Thats done like so:

RewriteEngine on

#Use this program to do the mappings
RewriteMap hosted prg:/etc/apache2/map.hosted.php

#This is for allowing www.user.elizium.net type urls as they're outwith the scope of the rewrite script as they're easy to map.
#See http://forum.modrewrite.com/viewtopic.php?t=1652&postdays=0&postorder=asc&start=10
RewriteCond %{HTTP_HOST} !^(www\.)?elizium\.net$ [NC]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} ^([^\.]+\.)?([^.]+)\.elizium\.net$ [NC]

RewriteRule .* /home/%2/web/%2\.elizium.net%{REQUEST_URI} [QSA,L]

#Ignore *.elizium.net
RewriteCond %{HTTP_HOST} !elizium.net$ [NC]
#Use the full request
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
#Throw the request through the map program
RewriteRule ^(.*) ${hosted:$1} [L,C]

I hope this is of use to people. Especially those folk looking for a php based rewrite script for Apache. Theres a lot of scope to expand the script. For example, the file data could be retrieved from a database and then stored in something like memcached if you were performing this sort of thing on a larger scale. Much better than having a large webserver config file that means the webserver has to be reloaded for each change.

The only problem I have with this, and I know its not insurmountable, is that the webserver user has to be able to read the files that users are storing. To allow this I’ve made the www-data user a member of the group all the users are members of. This also means that users can read each others files. So if somebody was to hardcode a password in a php file (for example) then it would be open to being read by any other user.

I also want to mention suPHP, it really does merit an article to itself, but as I’m not intending to cover it in depth (you can read all about it at the website) I’ll just say that I’ve enabled it on Wintermute to prevent users being able to do things like kill the web server processes via a php script. To do that, I installed the suphp-common Debian package. The default configs are all you need. For the non-Debian user, they look like this when taken together:

LoadModule suphp_module /usr/lib/apache2/modules/mod_suphp.so

suPHP_Engine on
AddHandler x-httpd-php .php .php3 .php4 .phtml
# # Use a specific php config file (a dir which contains a php.ini file)
suPHP_ConfigPath /etc/php4/cgi/

As usual comments on the above are most welcome. Especially quick links about solving my security issue!

Tags: , , , ,

Do you have anything to say?

Powered by Wordpress and Stripes Theme Entries (RSS) | Comments (RSS)