I'm trying to get a PHP script to work where it compares the IP of the person against a text file(one IP per line). If the person's IP is not in the text file, then he gets redirected to declined.html. I know I could use .htaccess for this but the IP list could get really, really long.
This is the code that I have so far:
<?php
$ipArray = file('ip.txt');
unset($allowed);
foreach ($ipArray as $ipTest) if (substr_count($_SERVER['REMOTE_ADDR'],trim($ipTest)) != "0") $allowed = true;
if ($allowed != true) {
header('location: /declined.html'); // the banned display page
die();
}
?>
I want to be able to call this script in every page that I only want certain IP's to see, this is what I'm trying to call it with:
<? include('ip_allow.php'); ?>
When I call the PHP script in the HTML page when my IP is NOT in ip.txt, it does not redirect me to /declined.html! How can I fix this? Again, my question is not how to use .htaccess, but how to fix my script! Thanks.
For IP filtering, it's best to do it as early as possible in the processing chain. In order of preference:
router
firewall
webserver
script
Most likely you don't have access to the router or firewall levels, but you CAN use Apache's mod_rewrite to have a dynamic block using an external file. Set up your "enabled IPs" file as follows:
a.b.c.d ALLOWED
b.c.d.e ALLOWED
c.d.e.f ALLOWED
etc...
It's basically "key value", where the key is the IP address
Then your mod_rewrite rules (I've numbered them for reference)
1. RerwriteMap ipfiltermap txt:/path/to/your/ips/list
2. RewriteCond %{REMOTE_ADDR} (.*)
3. RewriteCond %{ipfiltermap:%1} !ALLOWED
4. RewriteCond %{REQUEST_URI} !^/declined.html
5. RewriteRule .* /forbidden.html [L]
They work as follows:
Defines the allowed IPs as a "key value" pair, where the keys are the allowed IPs, and the value is the word "ALLOWED", as a mapping named "ipfiltermap"
Captures the remote address of the current request (the client IP)
Using the IP captured in step 2, look it up in the ipfiltermap, and see if the the IP does NOT have the word 'ALLOWED'
Specifically exempt your 'declined' page from the IP enforcement - if this wasn't here, disabllowed IPs would enter an infinite redirect loop
If all 3 of the RewriteConds match, then the user is from a forbidden IP, and should be redirected to the fobidden page.
Relevant Apache docs for all this are here.
If you place your configuration in your Apache configuration directly, it won't incur the speed penalties of .htaccess lookups -- but that would mean you'd need to reload the Apache configuration whenever the list is modified. (Though Marc's answer avoids this very nicely.)
The mod_authz_host.c is optimized for moderately fast searching. If you're reading a text file in your script every execution, you're already several times slower than Apache. Apache reads the configuration once, converts the IP addresses to a binary format once, and can then use very fast integer arithmetic to determine if hosts are allowed or not.
Furthermore, it's already debugged and working. It'd take you less time to deploy it than it would to find the bug in your current code -- and even then, your current code would -- on every access-controlled request -- re-read (and re-parse into an array) the textual description of IP addresses, convert the IP address from the remote peer into a text version, and then perform a very slow text-based comparison over the entire array.
If speed is of real importance, then you should investigate doing the access control via your system firewall. iptables has optimized routines to find matching IP addresses among a list of allowed or denied hosts and won't waste any time performing any protocol analysis. Of course, this is a much heavier all-or-nothing approach that would require an annoying separation of content among listening ports if some content is available for all.
I would guess you have files in the wrong directory, or PHP not working or some other more basic configuration issue, since I tested your code and it works fine for me.
The file() command includes the \n character at the end of each line, so each line is actually something like 0.0.0.0\n which is returning false every time.
Use this:
$ipArray = file('ip.txt', FILE_IGNORE_NEW_LINES); // Try specifying an ABSOLUTE path to this file.
$allowed = false; // Rather than unset($allowed), this will prevent notice errors too!
foreach ($ipArray as $ipTest) if ($_SERVER['REMOTE_ADDR'] == $ipTest) $allowed = true;
Also just to point out in your header line, Location should start with a capital letter and you should specify a full URI to the file:
header('Location: http://example.com/declined.html');
Related
Use PHP/Apache to restrict access to images
First, I know this is NOT a new topic, or even a duplicate that was discussed, but I want to propose a new aproach, that I don't know how to implement:
My idea is the following:
APACHE should deliver the file not PHP. This is highly critical
PHP should just do the authentication and re-send/forward the request with some trick/variable set)
the same-request is being re-sent and this second time the Rewrite Conditions should fail(due to the trick) and the file should be directly delivered.
Steps:
request is coming in(URL = http://somedomain.com/files/image1.png), apache forwards it to access.php
access.php checks that the user has access to the file
IF check is successful, then PHP sets an environment variable AUTHORIZED = true and makes an internal re-direct to the original URL.
Same request is coming in (SECOND time - this time with AUTHORIZED set) -> RewriteCond will NOT match(RewriteCond %{ENV:AUTHORIZED} !^$), and the file will be delivered.
I am not sure how to make a proper link between step 3 and 4. Not sure how that would be possible.
I am not sure what kind of mechanisms are available from PHP to set an apache environment variable, or something similar.
Any input would be appreciated.
Code samples:
.htaccess
RewriteEngine on
RewriteCond %{ENV:AUTHORIZED} !^$
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ access.php [L]
access.php
if ( 1 == 1 /* check if user is authorized*/ ) {
// set an environment variable for apache!!!
apache_setenv( 'AUTHORIZED', "true" ); //not working!
//forward the request
header( "Location:" . $_SERVER[ 'REQUEST_URI' ] );
exit;
} else {
header("HTTP/1.1 401 Unauthorized");
exit;
}
Some references:
https://serverfault.com/questions/374955/protecting-images-from-direct-access-by-checking-current-php-session-with-mod-re
Using PHP/Apache to restrict access to static files (html, css, img, etc)
I am not sure how to make a proper link between step 3 and 4. Not sure how that would be possible.
It isn't, at least not in practical terms. HTTP is stateless.
I'm not too familiar with the Apache life cycle, but any environment variable would last either:
Only for the request, in which case it wouldn't exist when the client made another request in response to the redirect
Globally, in which case every request (from every client) would be given access
In theory you could authorise an IP address (but IP addresses are shared so you'd be giving access to anybody who shared the IP … such as everyone in the same building as the client in many cases). So, that wouldn't be secure.
You could set a cookie and then have another mechanism check for that cookie … but then you'd either need to repeat the authentication or have a shared cookie that anyone could copy. So, that wouldn't be secure.
If you were working in C or Perl (mod_perl) then you could write an Apache module to hook in to the authentication/authorization part of the request cycle. But you're working in PHP and, as far as I know, nobody has ever written anything to let PHP play with Apache's internals (well, unless you count PHP.pm, but I've no idea how neatly you could tie that in with mod_perl).
For a "classic" website, one would create a /foldername/index.php for every web page. With WordPress, however, this is not the case. For example, if a page was created with WordPress whose URI was http://myblog.org/some_page, you would not find the folder www/myblog.org/some_page in your web host's FTP.
My question then, is, How can I serve up pages located at http://[MY_WEBSITE].com/[page_name] for any arbitrary page_name, without creating a new folder for every page_name?
One method would be to use the page_name as parameter to a common file and use that to serve the contents of the required page.
That behaviour is handled (in an Apache server) by a .htaccess file, wherein rewrite rules are defined. Rewrite rules basically capture incoming traffic and directs those requests to a file on the server (typically a single page which will act as a router).
The router is then responsible for taking the input URI (usually via $_SERVER["REQUEST_URI"] in PHP) and working out what to do with it, and ultimately what the output will be for that request.
As for a decent router, you could look at klein.php. Also, a brief example:
# htaccess file
RewriteEngine On
RewriteRule ^[^\.]+$ index.php
And the index.php:
$route = $_SERVER["REQUEST_URI"];
if($route === '/home')
{
echo 'This is the homepage';
}
You tell your server to rewrite the URL. Most servers do it in their own way, so to find out how to do it look at your server's documentation.
Wordpress uses templates that make use of the require() function and a foreach loop commonly called "The Loop" to retreive content.
Different pages are called using different templates. If you want to know exactly how this logic is calculated, look into this.
How do I get the value of an URL eg. www.example.php/folders/crowd? I'm trying to echo out crowd with PHP. I have tried using the $_SERVER ['SCRIPTNAME'] but I really cant get it to work.
To get the current directory of the file (which is just "crowd" in your "www.example.php/folders/crowd" example), use:
$cur_dir = basename(dirname($_SERVER[PHP_SELF]))
If you just want the file, then try this:
$cur_file = $_SERVER[PHP_SELF];
You can use parse_url, because scriptname will return the real php file execution "index.php" for example.
php > var_dump(parse_url('http://www.example.php/folders/crowd', PHP_URL_PATH));
// "/folders/crowd"
But if you want just the last part you can:
$tmp = explode('/', 'www.example.php/folders/crowd');
var_dump(end($tmp));
// "crowd"
Or another way:
var_dump(basename(parse_url('http://www.example.php/folders/crowd', PHP_URL_PATH)));
// "crowd"
Maybe you want to have a look at the Apache module mod_rewrite. Many web hosting provider offer this module. If you even operate a dedicated server owned by you, it's no problem to install / enable this module.
mod_rewrite can be used to transparently "transform" request URLs to requests for defined php scripts with query string parameters derived from the original request. This is often used for SEO-friendly URLs like http://mywebpage/article/how-to-cook-pizza-123.html. In this case a script might be invoked taking only the 123 from the URL as a parameter (e.g. http://mywebpage/article/how-to-cook-pizza-123.html => /article.php?id=123).
In your case you could use a configuration like this (put this into an .htaccess file or your Apache/vhost configuration):
RewriteEngine on
RewriteRule ^folders/[A-Za-z]+$ showfolder.php?user=$1 [L]
Your showfolder.php might look like this:
<?php
if (isset($_GET['user'])) {
echo htmlentities($_GET['user']);
} else {
echo "No parameter found.";
}
?>
Any word after folders/ that consists of letters (A-Z and a-z) will be taken as the user parameter of your php script. Then you can easily echo this value, fetch it from a database or whatever. For example, a request for /folders/crowd will result in your script being executed with this parameter: showfolder.php?user=crowd. The user of your website won't see anything of this hidden internal forwarding.
If you use some other web server software (nginx, ...): There are similar modules for other web server products, too.
Currently I let a single PHP script handle all incoming URLs. This PHP script then parses the URL and loads the specific handler for that URL. Like this:
if(URI === "/")
{
require_once("root.php");
}
else if(URI === "/shop")
{
require_once("shop.php");
}
else if(URI === "/contact")
{
require_once("contact.php");
}
...
else
{
require_once("404.php");
}
Now, I keep thinking that this is actually highly inefficient and is going to need a lot of unnecessary processing power once my site is being visited more often. So I thought, why not do it within Apache with mod_rewrite and let Apache directly load the PHP script:
RewriteRule ^$ root.php [L]
RewriteRule ^shop$ shop.php [L]
...
However, because I have a lot of those URLs, I only want to make the change if it really is worth it.
So, here's my question: What option is better (efficiency-wise and otherwise) and why?
Btw, I absolutely want to keep the URL scheme and not simply let the scripts accessible via their actual file name (something.php).
So, here's my question: What option is better (efficiency-wise and otherwise) and why?
If every resource has to run through a PHP based check, as you say in your comment:
some resources are only available to logged in users, so I get to check cookies and login state first, then I serve them with readfile().
then you can indeed use PHP-side logic to handle things: A PHP instance is going to be started anyway, which renders the performance improvement of parsing URLs in Apache largely moot.
If you have static resources that do not need any session or other PHP-side check, you should absolutely handle the routing in the .htaccess file if possible, because you avoid starting a separate PHP process for every resource. But in your case, that won't apply.
Some ideas to increase performance:
consider whether really every resource needs to be protected through PHP-based authentication. Can style sheets or some images not be public, saving the performance-intensive PHP process?
try minifying resources into as few files as possible, e.g. by minifying all style sheets into one, and using CSS sprites to reduce the number of images.
I've heard that nginx is better prepared to handle this specific kind of scenario - at least I'm told it can very efficiently handle the delivery of a file after the authentication check has been done, instead of having to rely on PHP's readfile().
The PHP approach is correct but it could use a bit of improvement.
$file = $uri.".php";
if (!is_file($file)) { header("Status: 404 Not Found"); require_once(404.php); die(); }
require_once($uri.".php");
OK, as for efficiency - htaccess version with regexp and php version with single regexp and loading of matching file would be faster than many htaccess rules or many php if - else
Apart from that, htaccess and php way should be similar in efficiency in that case, probably with little gain with htaccess (eliminating one require in php)
RewriteRule ^([a-z]+)$ $1.php [L]
and rename root.php to index.php.
I have a website that responds on *.domain.com.
Going to x.domain.com or y.domain.com should produce the same web page.
What * is I do not know, but it is and important piece of info since we track things based on it.
When moving to wordpress we ran into a pretty severe problem. It seems to generate links (using get_page_link) with the domain which is set in the admin.
This will not work for us because we can't find a way to tell wordpress to generate links without the domain (why does it do this anyway?!) and every time a link is clicked the browser goes from: x.domain.com to domain.com (since domain.com is what we have in the admin).
Unfortunately WordPress is architected such that it is really hard to get rid of the domain component of URLs. But all is not lost! Read on as the answer to your question requires a bit of background.
The WordPress team made the decision to require the user of a site to hardcode the site domain either in the database via an admin console, which you can see in the following screen shot, of via PHP which we'll discuss below:
You might ask what the difference is between the the two URLs? Even I find it confusing because I almost never need anything other them to have them both set to the root URL and as it's not important for your question I'll just gloss over that detail. If you are interested though you can learn more here:
Changing The Site URL
Editing wp-config.php: WordPress Address (URL)
Giving WordPress Its Own Directory
Moving along, the other option is to hardcode the two PHP constants WP_SITEURL and WP_HOME in the /wp-config.php file which can be found in the root of a WordPress installation. Those two lines might look like this in your /wp-config.php file:
define('WP_HOST','http://domain.com');
define('WP_SITEURL','http://domain.com');
The good news is that you can define both of them dynamically based on the current domain your site is serving from (I'm going to assume you have both your DNS server and your Apache web server configured for wildcard DNS.) You can use the following code to match any subdomain name consisting of letters and numbers:
$root_domain = 'domain.com'; // Be sure to set this to your 2nd level domain!!!
$this_domain = $_SERVER['SERVER_NAME'];
if (!preg_match("#^([a-zA-Z0-9]+\.)?{$root_domain}$#",$this_domain)) {
echo "ERROR: The domain [$this_domain] is not a valid domain for this website.";
die();
} else {
define('WP_HOME',"http://{$this_domain}");
define('WP_SITEURL',"http://{$this_domain}");
}
The bad news is you may have some "artifacts" to deal with after you get it working such as how URLs are handled for image URLs stored in the database content (which may or may not end up being a problem) or for Google Maps API keys, etc. If you have trouble with them let me suggest you post another question here or even better at the new WordPress Answers Exchange also run by the same people as StackOverflow.
As for telling WordPress how to generate links, there are filters you can "hook" but in my quick testing I don't think you need it because WordPress will generate the links for whatever domain happens to be your current domain. Still if you do find you need them you can do it although be prepared to be overwhelmed by all the add_filter() statements required! Each one controls one of the different ways links can be generated in WordPress.
Here is the hook filter function and the 40+ add_filter() calls; you might not need them all but if you do here they are:
function multi_subdomain_permalink($permalink){
$root_domain = 'domain.com';
$this_domain = $_SERVER['SERVER_NAME'];
if (preg_match("#^([a-zA-Z0-9]+)\.?{$root_domain}$#",$this_domain,$match)) {
$permalink = str_replace("http://{$match[1]}.",'http://',$permalink);
}
return $permalink;
}
add_filter('page_link','multi_subdomain_permalink');
add_filter('post_link','multi_subdomain_permalink');
add_filter('term_link','multi_subdomain_permalink');
add_filter('tag_link','multi_subdomain_permalink');
add_filter('category_link','multi_subdomain_permalink');
add_filter('post_type_link','multi_subdomain_permalink');
add_filter('attachment_link','multi_subdomain_permalink');
add_filter('year_link','multi_subdomain_permalink');
add_filter('month_link','multi_subdomain_permalink');
add_filter('day_link','multi_subdomain_permalink');
add_filter('search_link','multi_subdomain_permalink');
add_filter('feed_link','multi_subdomain_permalink');
add_filter('post_comments_feed_link','multi_subdomain_permalink');
add_filter('author_feed_link','multi_subdomain_permalink');
add_filter('category_feed_link','multi_subdomain_permalink');
add_filter('taxonomy_feed_link','multi_subdomain_permalink');
add_filter('search_feed_link','multi_subdomain_permalink');
add_filter('get_edit_tag_link','multi_subdomain_permalink');
add_filter('get_edit_post_link','multi_subdomain_permalink');
add_filter('get_delete_post_link','multi_subdomain_permalink');
add_filter('get_edit_comment_link','multi_subdomain_permalink');
add_filter('get_edit_bookmark_link','multi_subdomain_permalink');
add_filter('index_rel_link','multi_subdomain_permalink');
add_filter('parent_post_rel_link','multi_subdomain_permalink');
add_filter('previous_post_rel_link','multi_subdomain_permalink');
add_filter('next_post_rel_link','multi_subdomain_permalink');
add_filter('start_post_rel_link','multi_subdomain_permalink');
add_filter('end_post_rel_link','multi_subdomain_permalink');
add_filter('previous_post_link','multi_subdomain_permalink');
add_filter('next_post_link','multi_subdomain_permalink');
add_filter('get_pagenum_link','multi_subdomain_permalink');
add_filter('get_comments_pagenum_link','multi_subdomain_permalink');
add_filter('shortcut_link','multi_subdomain_permalink');
add_filter('get_shortlink','multi_subdomain_permalink');
add_filter('home_url','multi_subdomain_permalink');
add_filter('site_url','multi_subdomain_permalink');
add_filter('admin_url','multi_subdomain_permalink');
add_filter('includes_url','multi_subdomain_permalink');
add_filter('content_url','multi_subdomain_permalink');
add_filter('plugins_url','multi_subdomain_permalink');
add_filter('network_site_url','multi_subdomain_permalink');
add_filter('network_home_url','multi_subdomain_permalink');
add_filter('network_admin_url','multi_subdomain_permalink');
While brings us to the final point. There is functionality in WordPress that attempts to ensure every URL that is loaded is served via its canonical URL which in general is a web best practice, especially if you are concerned with optimizing search engine results on Google and other search engines. In your case, however, if you really do not want WordPress to redirect to your canonical URL then you need to add a redirect_canonical filter hook and tell WordPress not to do it.
What follows is the code to make sure any page that serves as "x.domain.com" stays on "x.domain.com" even if all the URLs are filtered to be "domain.com". That may not be the exact logic you need but I'm just showing you the building blocks of WordPress so you'll be able to figure out the logic that you require.
A few final details about this function call; parameters #3 and #4 refer respectively to the priority (10 is standard priority so this hook will not be handled special) and the number of function arguments (the 2 arguments are $redirect_url and $requested_url.) The other thing to note is that returning false instead of a valid URL cancels the canonical redirect:
add_filter('redirect_canonical','multi_subdomain_redirect_canonical',10,2);
function multi_subdomain_redirect_canonical($redirect_url,$requested_url){
$redirect = parse_url($redirect_url);
$requested = parse_url($requested_url);
// If the path+query is the same for both URLs, Requested and Redirect, and
if ($redirect['path']+$redirect['query']==$requested['path']+$requested['query']) {
// If Requested URL is a subdomain of the Redirect URL
if (preg_match("#^([a-zA-Z0-9]+).{$redirect['host']}$#",$requested['host'])) {
$redirect_url = false; // Then cancel the redirect
}
}
return $redirect_url;
}
That's about it. Hope this helps.
-Mike
do you have any control over your hosting? Maybe you can use the rewrite module in apache, if you are using apache.
In httpd.conf add:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^x\.domain\.com
RewriteRule ^(.*)$ http://www.domain.com/x/$1
RewriteCond %{HTTP_HOST} ^y\.domain\.com
RewriteRule ^(.*)$ http://www.domain.com/y/$1
You can change the way you pass the variable "v" too
RewriteRule ^(.*)$ http://www.domain.com/$1&var=v
I didn't try the code, but i'm pretty sure that at least it will open your mind to a new way to deal with this issue --one that doesn't involve so much coding.
Cheers.
A.