Using SSH to run remote commands using PHP. A cheat guide.

Posted on March 8th, 2009 in php by Myles Eftos || 1 Comment

TwitThis || StumbleUpon || Digg it || del.icio.us || Reddit

I’m working on a soon-to-be-released project that needed to run commands on a Linux server. Whilst it would be possible to use something like the exec command to run it, this would mean that the user that Apache was running as would have to have permissions to run the commands, which is less than cool. I could have messed around with sudo, but even that would open up some gaping holes, as all other websites hosted on the same box could theoretically run the same commands.

As it turns out, there is a PECL project that allows you to remotely login to a server using SSH, which would actually kill a number of birds with one stone:

  1. I can sandbox the commands that get run, by setting a special user that only has access to commands that are needed (using sudo)
  2. The web app would be able to talk to multiple servers, which wouldn’t have been possible with exec alone

The flow is simple: Login to the server - I’m using a username/password pair at the moment, but only because I haven’t been able to get public key exchange working on the server yet (interestingly, it works if I call the code from the command line), run the command, then check the output and response. There was a slight issue here,  ssh2_exec returns a pointer to a stream, which needs to be read. If there is no response (some programs complete without returning anything), then the process would block indefinately. Also, if the program fails, it might not output anything to stdout, instead outputting text to stderr, AND you miss out on checking the return status code (which quite often gives you some interesting information about the status of the program).

To get around this, I wrote this really simple bash script, that runs the command on your behalf and wraps the stdout, stderr, pwd and result in an XML envelope ready for parsing. Because you will always get the envelope returned (unless the process daemonises) you won’t get the blocking problem.


#!/bin/sh

tmp_stderr=`mktemp`

output=`$* 2>$tmp_stderr`
result=$?
error=`cat $tmp_stderr`
rm $tmp_stderr

echo “<?xml version=\”1.0\” encoding=\”UTF-8\”?>”
echo “<xmlsh>”

if [ -n “$output” ]
then
echo ” <stdout>”
echo ” <![CDATA[”
echo $output
echo ” ]]>”
echo ” </stdout>”
fi

if [ -n “$error” ]
then
echo ” <stderr>”
echo ” <![CDATA[”
echo $error
echo ” ]]>”
echo ” </stderr>”
fi

echo ” <meta>”
echo ” <pwd>$PWD</pwd>”
echo ” <return>$result</return>”
echo ” </meta>”
echo “</xmlsh>”
In a nutshell, when you call the script, it runs the program supplied as an argument, then pipes the stderr out to a temporary file, and pipes stdout into a variable. It wraps this, and the current working directory and return value in XML, and prints it out. Pretty simple, but it works.

Wanted: Dead or Alive. Some PHP developers

Posted on February 13th, 2008 in php by Myles Eftos || 1 Comment

TwitThis || StumbleUpon || Digg it || del.icio.us || Reddit

I’m in a bit of a predicament at the moment. As many of you know, I’m back doing the web consultancy thing full time again after a 18 month hiatus. I made a couple of rules that I’m trying hard not to break:

  1. I’m not supporting PHP4
  2. I’m not supporting or modifying badly written OpenSource projects  (basically crossing out WordPress, osCommerce or phpBB jobs)

Which (maybe un-)suprisingly I get asked for a lot. As a result, I’m looking for some PHP developers that haven’t become totally jaded by years of PHP4 abuse that I can sub-contract out to. I’d say I’m looking for mid-level developers, I’ll be there to help out find out WHAT needs to be done, and HOW it should be done - I just need someone to actually do the work.

I’m pretty anal about coding standards and software practice, so things like SVN are a must. I have a development server if you need an environment to work in.

If you think you might fit the bill, and want to work with someone who’s been around the development block a few times, email me on myles@madpilot.com.au with a short CV and your rates.

Be a good netizen: Use the correct HTTP response code

Posted on October 7th, 2007 in usability, SEO, software design process, php by Myles Eftos || 2 Comments

TwitThis || StumbleUpon || Digg it || del.icio.us || Reddit

Remember the good ol’ days back before dymanic websites where pages had .html extensions and when you tried to access a page that didn’t exist you got an ugly, yet reassuring 404 Not found page? The significance of this page is actually pretty important - not only does it tell the user that the page is not found but it returns a special HTTP status that tells web spiders the same thing. As web developers, sometimes we forget that humans aren’t the only ones accessing our pages, and as a result don’t use the correct HTTP response codes to denote what is going on.

What the hell is a HTTP response code?

When your web browser makes a request to a web server, the web server will return a status code as well as the web page, which tells your browser what has happened. This response is usually made up of two parts: a number (which is for any spiders or bots that might be accessing the web site) and a string (which is for humans) and back when everything was static the web server took care of everything.

Unfortunately for web developers, in this environment of database driven web sites, we often don’t have the luxury of letting the server take care of everything, so this article aims to show you that it isn’t that difficult to do HTTP responses correctly. As I will show later, this can adversely affect your ranking in your favourite search engine.

Let’s try it out

Firstly, lets see what happens when you actually make a request - you can see what is going on using another old school application: Telnet.

Open up command line or Terminal.app or terminal depending on you flavour and type the following:

telnet madpilot.com.au 80

You will get a prompt and type the following (Windows users might not see anything as the telnet client won’t echo what you type):

HEAD / HTTP/1.1
Host: madpilot.com.au

…and hit enter twice - you should see something like:

HTTP/1.1 200 OK
Server: Mongrel 1.0.1
Status: 200 OK
Cache-Control: no-cache
Content-Type: text/html; charset=utf-8
Content-Length: 4123

The first line of the response is the important bit - it tells the web browser that the response conforms to HTTP version 1.1 and more importantly the response code is 200 and the response type is OK! The 200 type is the most common response you will come across, it means that the page was found and served up correctly. Generally your web server WILL take care of this one for you. Let’s look at how you can change that status code.

I’ll use PHP as an example, because it is still the most common dynamic language - but you can do this in any language, leave a comment if you would like an example of how to do it in another dialect. It is all very simple - BEFORE you output any HTML, call the header() function as such:

header("HTTP/1.1 404 Page Not Found");

As you have probably guessed, this will tell the browser that the page it requested was not found. Why would you want to do that? Obviously if the script is being run, it has been found? Well, yes - that is true, however, when the HTTP specification was written, CMSs and dynamic product catalogues weren’t even thought about - so we need to think a little bit differently.

Let’s look at an example: Your customer has requested to see the details for product #10 by browsing to

http://www.yourcomany.com/products/view/10

Product #10 exists, so we serve up the details, but what if the customer decides to see what product #20 is? If product 20 doesn’t exist, then what should we do? One option is to print out “Product not found” which is fine for the user, but what happens if your favourite search engine tries to hit product #20? If you maintain the default action the search engine will receive a 200 OK status which makes it think that the product exists and it will index it! This just pollutes the search engines’ index and hurts your ranking, which is bad. So what we can do is serve up the 404 header from above and this let’s both the user AND the search engine know that what they have requested doesn’t exist.

So what other response codes can we use? There is a complete list here, but I’ll run through a couple of the common ones:

301 Moved Permanently: This response code means the resource that has been requested USED to live here but has now moved somewhere else and will never return. Returning this status code is extremely important if you are changing the structure of your website, as you can tell the browser where it needs to go to get the resource. More importantly, it also tells your search engine to update it’s index with the new URL. You need to supply the new URL as part of the request, so it looks something like this:

header("HTTP:1/1 301 http://www.yoursite.com/new-url");

302 Found: The 302 is actually generally used incorrectly. The most common use is to redirect a user to another page TEMPORARILY which is actually what the 303 code is for. unfortunately, not all browsers support 303 and actually expect a 302 in this case. So who are we to argue? If you are a PHP developer, you have probably used

header("Location http://www.yoursite.com/somewhere");

before - this is exactly what this does.

403 Forbidden: If you wanted to really play HTTP right, you would return this code every time someone tried to access a private URL when they weren’t logged in. It means the server knows what you are trying to do, but isn’t going to let you do it.

404 Page not found: This has been covered - basically if the resource the agent wants doesn’t exist, you serve this up.

410 Gone: This page you are looking for used to exist, but it doesn’t anymore. In fact there isn’t even a new URL, so if you are a search engine, just forget about it. Whether search engines listen to this, I’m not sure, but it can’t hurt.

500 Server Error: Something went wrong with the server. I would throw this up if there is an error that is stopping the page from loading, such as a missing database or a broken web service or similar.

Don’t forget that you can also server up content to the browser (in fact, if you don’t humans will just get a blank page), so it is recommended that you serve up a nice friendly message to your visitors explaining what happened.

So there you go - now there is no excuse for serving up errors to your users and forgetting about our automated friends. So when you are writing your next kick-arse web app, spare a thought for the visitors that aren’t so good at parsing human talk.

PHP 4 being put out to pasture

Posted on July 16th, 2007 in php by Myles Eftos || 4 Comments

TwitThis || StumbleUpon || Digg it || del.icio.us || Reddit

Ok, I might be a bit of a Rails zealot now, but I still have a sweet spot for PHP. For many years it was my language of choice, and even today, server hosting or legacy applications still means that I have to throw around the < ?php ?> tag.

I just read on the official PHP website, that as of the end of this year, PHP 4 will no longer be updated, bar crucial security patches. This is a big thing, as many web hosting companies still only support PHP 4 as it isn’t possible to run PHP 5 on the same apache server with out resorting to CGI or proxy work-arounds. This basically means that you have 5 months to make sure that your webiste runs on PHP 5.

The PHP website has a guide to migration, and luckily, most of the time things are fairly smooth. Were you might get in to trouble is the new object model. PHP 5 has a brand new, closer-to-real OOP model that isn’t always completely backwards compatible with it’s older sibling. For example, the __construct() method is called as a constructor, rather than function with the same same as the class as per PHP 4. PHP 5 also supports private, protected and public accessors and methods, as well as native XML and SOAP support.
At this point if, you may want to try running all of your websites as CGI scripts via the PHP5 interpreter to make sure that they can run ok, and if they do, start moving everything over to ensure a smooth transition.

Image verification for comments in WordPress 2.0

Posted on April 1st, 2006 in php by Myles Eftos || 7 Comments

TwitThis || StumbleUpon || Digg it || del.icio.us || Reddit

I’m sick of blog spam. It is such a pain to have to mark it all as spam (And what exactly does that function do? I’ve seen the same spam come through many times, so it doesn’t seem to learn…)

Anyways, here is my quick and dirty hack to add a image verification box on the comments page (Warning WordPress hack ahead - I’ll bother to learn the plugin system one day…)

This hack requires the following PHP libraries installed on your server: Freetype, GD and mcrypt.

Create a new file in [path_to_your_blog]/wp-image-verify.php

Insert the following code:

  1. < ?php
  2. require( dirname(__FILE__) . ‘/wp-config.php’ );
  3. $verifier = $_REQUEST[‘verifier’];
  4. $display_string = decrypt($verifier);
  5. $image_details = imagettfbbox (10, 0, ‘/usr/share/fonts/TTF/tahoma.ttf’, $display_string);
  6. $image = imagecreatetruecolor($image_details[4] - $image_details[5], (0 - $image_details[5]) - (0 - $image_details[3]) + 1);
  7. imageantialias($image, true);
  8. $background = imagecolorallocate($image, 255, 255, 255);
  9. imagefill($image, 0, 0, $background);
  10. imagettftext($image, 10, 0, $image_details[0] + 1, 0 - $image_details[5], imagecolorallocate($image, 119, 119, 119), ‘/usr/share/fonts/TTF/tahoma.ttf’, $display_string);
  11. header(“Content-type: image/png”);
  12. imagepng($image);
  13. ?>

Note - you will probably (ok will) have to change the path of the font as /usr/share/fonts/TTF/tahoma.ttf is a font I copied from my windows directory.

Open [path_to_your_blog]/wp-comments-post.php and add the following lines at around line 25:

  1. $verifier = trim($_POST[‘verifier’]);
  2. $verifier_compare = decrypt($_POST[‘verifier_hash’]);

Open [path_to_your_blog]/includes/comments-functions.php and add the following lines at the top of the file:

  1. // image verifier functions - by Myles Eftos
  2. $key = “afedss”; // Enter a 5 character string
  3. $iv = “sdfesdf”; // Enter a 7 character IV
  4. function random_string($length = 6) {
  5. $str = ;
  6. for($i = 0; $i < $length; $i++) {
  7. $str .= chr(rand(ord(‘A’), ord(‘Z’)));
  8. }
  9. return $str;
  10. }
  11. function encrypt($string) {
  12. global $key;
  13. global $iv;
  14. return base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, $string, MCRYPT_MODE_ECB, $iv));
  15. }
  16. function decrypt($string) {
  17. global $key;
  18. global $iv;
  19. return chop(mcrypt_decrypt(MCRYPT_DES, $key, base64_decode($string), MCRYPT_MODE_ECB, $iv));
  20. }

Replacing the $key and $iv variables with a random 5 and 7 cahracter string respectively.

Finally find the comments.php file in you theme directory - if you are using the default theme it is at [path_to_your_blog]/wp-content/themes/default and add the following lines after the url textbox (Around line 89 in the default template):

  1. < p>

  2. < input type=“text” name=“verifier” id=“verifier” size=“22″ />
  3. < label< small>Enter the string you see here: < img src=“< ?php echo get_option('siteurl'); ?>/wp-image-verify.php?verifier=< ?php echo urlencode ($image_hash) ?>” alt=”" />< / small>  < / label>
  4. < input type="hidden" name="verifier_hash” value=”< ?php echo $image_hash; ?>” />
  5. < /p>

All done :)

« Previous Entries