Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Sep 20, 2016

Migrating payment extensions to OpenCart 2.2 and 2.3

This is an incomplete list of changes.

In OpenCart 2.2.x
  • In catalog/model/ there is no function currency->getCode()
    (Fatal error: Call to undefined method Cart\Currency::getCode() in ...on line ...)
    //$currency = $this->currency->getCode(); // OC <= 2.1
    $currency = $this->config->get('config_currency'); // OC 2.2
  • In controllers, the prefix to default templates has been automatically added.
    Remove the "default/template" from load->view()
In OpenCart 2.3.x
Along with the listed changes for OpenCart 2.2.x we have noticed these changes:
  • Directory structure has been changed, from catalog/controller/payment/ to catalog/controller/extension/payment. This affects view files, breadcrumbs in the admin as well as the controller class name, files and URL routes.



Feb 18, 2016

Using Wordpress AJAX on Frontend

wp_ajax_* callback functions will return 0 if they are called on the frontend by anonymous users.

This is because of security reasons, but Wordpress has prefixes for "unsecure" calls: wp_ajax_nopriv_*.

Both of the hooks need to be defined to work with logged in and anonymous users.

In functions.php define:
add_action( 'wp_ajax_call_stuff', 'my_callback' );
add_action( 'wp_ajax_nopriv_call_stuff', 'my_callback' );
function my_callback() {
    print_r($_POST);
    exit;
}

Another thing to add is the ajaxurl variable, which is not included on the frontend by default. You can do this in many ways, but I figured the best thing to do is to use wp_localize_script().
wp_localize_script('my_main_script', 'myPrefix', array( 'ajaxurl' => admin_url( 'admin-ajax.php')));

Then, you can call the AJAX request like this:
jQuery.post(myPrefix.ajaxurl, {
  'action': 'call_stuff',
  'whatever': '1'
 }, 
 function(response){
  alert('The server responded: ' + response);
 }
);

Nov 11, 2014

Developer notes for migrating extensions to OpenCart 2

My condolences to anyone reading this.

Please note that this is not a full list of changes, as I've only been working with payment gateway extensions at the moment.

Opencart released version 2 in October 2014 with major layout and structure changes. When looking at the new version at first glance the codebase seems the same and you'd think extensions should work by default. They won't.

The first thing to notice is that they completely changed templates files - HTML structure, forms. TPL files should be completely rewritten. But at least the new admin layout looks nice.
  • Admin template files use forms, columns.
  • Catalog template files use a new default page layout
  • Any custom error template files need to adhere to the new layout.

Methods visibilty in classes is changed to public. Using private or protected will not work.
class ControllerPaymentPPStandard extends Controller {
public function index() { ...

Catalog model has a new property (terms & conditions URL) that is returned in the method data array.:
'terms'      => '',

There is no render function in controllers in version 2. Using the call
$this->render(); has been changed to
return $this->load->view($template_file, $data);
Important thing to note here is that $data must be a local variable, not a class attribute.

Redirection is changed from
$this->redirect(...)
to
$this->response->redirect(...)


Methods
$this->model_checkout_order->confirm() and
$this->model_checkout_order->update() have been changed to
$this->model_checkout_order->addOrderHistory();


Nov 14, 2013

Encoding Web Shells in PNG IDAT chunks




Roses are red,
violets are blue,
images contain code,
you're hacked.


I've just had the privilege to have my mind blown, while reading this article:
Encoding Web Shells in PNG IDAT chunks. All credits go to Phil for this one, an IT consultant and hacker from down under. You should check out his other articles. Fascinating stuff.



In layman's terms, let's say you have a web page that allows user upload of images. You're feeling pretty safe about it, because when the image is uploaded, you immediately open and manipulate it in GD, a PHP image library, before you save it on the server. If you can't open the file, it's not a valid image. That should destroy all malicious code stored within the image, right?

That's right. It should.

But what if an image appears normal, and after you resize it in GD, a PHP shell appears out of thin air?

An image that contains a PHP shell "<?=$_GET[0]($_POST[1]);?>" when resized to 32x32 with GD.


There's no way around it. You can't really prevent it. And to make things worse, you're not even checking the file extension.


If you reverse the process of how the image is generated, you can encode all sorts of data in the image. When the image is then manipulated with GD, it produces plain text data within the image.
Sounds simple enough, but there are a few hoops one needs to jump through, to engineer such an image.
First, PHP code must be compressed, then reverse the PNG filtering process and finally embedding the data as raw pixels.


Depending on what the server does with this image, there are a few more tricks to be done.
If the file is resized with imagecopyresampled(), the payload needs to be encoded in a series of rectangles or squares.

Et voilĂ ! Your PHP shell.



Oh sh*t!
But what can I do about it?

Like I said, not much. Without getting in too much detail on how this sorcery is done (you can check the source article for that), all you can do is focus on the prerequisites that enable this hack to work. Just uploading the image is, thankfully, not enough.

If you're a developer, don't be stupid. Don't do stupid things. Validate user input. Sanitize data. Have total control over how and where the files get saved. Triple check file and directory permissions and file extensions. There are many image hosting services and with the cloud becoming more and more popular, there are things like Amazon S3 you can use, to host data on third party.

Also, having total control over the file extension is not foolproof. You see, if your script contains a Local File Inclusion vulnerability (LFI) as well as user image upload, then my friend, I have bad news for you.
An attacker can just as well exploit the LFI with the path to the .png on the server.


Oh, you have all your include()-s and require()-s prefixed and suffixed? Tell me all about it! But while you're at it, have a look at this stackexchange debate. [*sound of explosion*]

If you're a user innocently hosting your web page somewhere, you can hope that your hosting provider has tight security, but also check file and directory permissions. Anything that isn't specificaly meant for upload, shouldn't have write access for apache. That typically means 644 or -rw-r--r--, for you. If you have .htaccess enabled, you can disable PHP execution on directories with user upload. See how below. Make sure it's not writable by anyone else.

If you're a sysadmin, you can expect your users to run all kinds of outdated opensourcy mumbo jumbo, which is like magnets for abusers. But you can't just mess with their files and do as you please. You've g0t r00t, and that's your real power. Figure out which directories can be written to by the web server, and stop PHP execution on these directories.
For example, Wordpress:

<Directory /home/test/www/wordpress/wp-includes>
php_flag engine off
</Directory>

<Directory /home/test/www/wordpress/wp-content/uploads>
php_flag engine off
</Directory>

Even though these directories contain PHP scripts, they're never called directly by URL. They are require()-d or include()-d by index.php originally. However, this raises another issue - source code disclosure, if done sloppy. The example above is quick and dirty. Yes, sloppy. Do some work.
Also, you can use a Web Application Firewall, like mod_security or things like that. Unfortunately, they wouldn't help in this my-png-is-a-shell situation. But they can solve a lot of other potential problems.


Well thanks, Jean! You've *really* helped me out with this information! ...NOT!

I've said it once, I've said it twice, I'll say it again.
There is no standard solution for this.
You're gonna have to find every hole through which an attacker can crawl through, fix every sensitive information disclosure (don't display error messages on the page, don't display source code - with php_flag engine off it will be displayed!). Don't be sloppy, don't be stupid.

Feeling safe yet?
If not, you can give us a call, and we can do some penetration testing for you.

Oct 5, 2013

Restore panoramas (cubic tiles) from exported Pano2VR

With this simple little script you can restore the full size cubic faces of the panorama which was exported using Pano2VR.

Pretty useful, if you deleted the original by mistake ;)

To convert the cubic projection back to equirectangular, you can also use Pano2VR, but for the input source you choose "Cubic", load all six cube faces, and export transformation.


Pano2VR's panoramas are exported as tiles in a cubic projection. The file naming scheme is as follows:
c[CUBE FACE]_l[RESOLUTION LEVEL]_[X]_[Y].jpg
for example:
c0_l0_0_0.jpg.

The most detailed resolution level is "0", so that level is used, and other tiles are disregarded.
Cube faces range from 0 to 5, and X and Y are the row and column number.

The script is written in PHP, and should be run in a CLI.

error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('memory_limit', '2G');
date_default_timezone_set('Europe/Ljubljana');

$dir = 'tiles/';

foreach(range(0,5) as $c) {
 $i = 0;
 $tiles = array();
 $height = 0;
 do {
  $width = 0;
  $j = 0;
  do {
   $fn = fname($c,$i,$j);
   if(file_exists($fn)) {
    $tiles[$i][$j] = $fn;
    list($w, $h) = getimagesize($fn);
    $width += $w;
   }
   $j++;
  } while(file_exists(fname($c,$i,$j)));
  $j--;
  $i++;
  $height += $h;
 } while(file_exists(fname($c,$i,$j)));
 $i--;
 
 echo $width.' '.$height."\n";
 $im = imagecreatetruecolor($width, $height);
 $y = 0;
 foreach($tiles as $row) {
  $x = 0;
  foreach($row as $tile) {
   $tile = imagecreatefromjpeg($tile);
   $src_w = imagesx($tile);
   $src_h = imagesy($tile);
   imagecopy($im, $tile, $x, $y, 0, 0, $src_w, $src_h);
   $x += $src_w;
   imagedestroy($tile);
  }
  $y += $src_h;
 }
 imagejpeg($im, "$c.jpg", 70);
}

function fname($c,$i,$j) {
 global $dir;
 return "{$dir}c{$c}_l0_{$i}_{$j}.jpg";
}

Tiles

Restored cube faces

Restored panorama 



Mar 25, 2013

Java, PHP :: RSA Asymmetric Encryption

Information Security and Privacy was a pretty fun and interesting class I had in college, I learned quite a lot of interesting new stuff, but when it came down to it, in practice, I had some problems implementing encrypted communication between Android (Java) frontend and PHP backend because of a tiny little detail.
Algorithm of choice was RSA and the most important part is to use "RSA/ECB/PKCS1PADDING" algorithm when calling Cipher instance in Java, other stuff is pretty straightforward.

RSA works like this.
Imagine a scenario where Alice sends an encrypted message to Bob.
As you can see, Alice encrypts data with Bob's public key, and only Bob can decrypt it, because only he has his private key.

Here's the Java code (Warning, some Android elements ahead)
public class Encryption {
 private static Key  privKey;
 private static PublicKey pubKey;
 private static PublicKey servPub;
 
 private static String  tag  = "Encryption";
 private static String[]  alg  = {"RSA","RSA/ECB/PKCS1PADDING"};
 private static String  hash  = "SHA1";
 private static String  serverPubKeyB64 = ""; //Bob's public key here
 
 
 public static String encrypt(String data) {
  return b64(encrpyt(data.getBytes()));
 }
 
 public static byte[] encrpyt(byte[] data) {
  genKey();
  try {
   Cipher c1 = Cipher.getInstance(alg[1]);
         c1.init(Cipher.ENCRYPT_MODE, getServPub());
         return c1.doFinal(data);
  } catch(Exception e) {
   Log.e(tag, "encrpyt", e);
  }
  return new byte[0];
 }
 
 public static void genKey() {
  if (privKey == null || pubKey == null) {
   Log.i(tag, "generating key");
   try {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg[0]);
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    privKey = kp.getPrivate();
    pubKey = kp.getPublic();
   } catch (Exception e) {
    Log.e(tag, "genKey", e);
   }
  }
 }
 
 public static PublicKey getServPub() {
  if(servPub == null) {
   try {
    byte[] encodedPublicKey = b64decode(serverPubKeyB64);
    
    KeyFactory keyFactory = KeyFactory.getInstance(alg[0]);
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
    
    return publicKey;
   } catch(Exception e) {
    Log.e(tag, "getServPub", e);
   }
  }
  return servPub;
 }
 
 public static PublicKey getPubKey() {
  genKey();
  return pubKey;
 }
 
 public static String getPubKeyB64() {
  PublicKey key = getPubKey();
  if(key != null) {
   return b64(getPubKey().getEncoded());
  } else {
   Log.e(tag, "getPubKeyB64 - key is null");
   return "";
  }
 }
 
 public static String b64(byte[] b) {
  return Base64.encodeToString(b, Base64.DEFAULT);
 }
 
 public static byte[] b64decode(String str) {
  return Base64.decode(str, Base64.DEFAULT);
 }
 
 public static String sha1(String data) {
  return sha1(data.getBytes());
 }
 
 
 public static String sha1(byte[] data) {
  return formatString(sha1bytes(data));
 }
 
 public static byte[] sha1bytes(byte[] data) {
  try {
   MessageDigest md = MessageDigest.getInstance(hash);
   return md.digest(data);
  } catch(Exception e) {
   Log.e(tag, "sha1", e);
   return new byte[0];
  }
 }
 
 @SuppressLint("DefaultLocale")
 public static String formatString(byte[] data) {
  StringBuilder sb = new StringBuilder();

  for (byte b : data) {
      sb.append(String.format("%02X", b));
  }
  
  return sb.toString().toLowerCase();
 }
}
And the PHP code:
class Encryption {

    public $pubkey = '...';

    public $privkey;
 
 public function __construct() {
  $this->privkey = openssl_pkey_get_private(file_get_contents('....pem'));
 }

    public function encrypt($data) {
        if (openssl_public_encrypt($data, $encrypted, $this->pubkey))
            $data = base64_encode($encrypted);
        else
            throw new Exception('Unable to encrypt data.');

        return $data;
    }

    public function decrypt($data) {
        if (openssl_private_decrypt(base64_decode($data), $decrypted, $this->privkey))
            $data = $decrypted;
        else
            $data = '';

        return $data;
    }
}

Jan 8, 2013

PHP: shortest and easiest GeoIP (IP to Country)

Without using a database of IP addresses or an "external API" (not completely true), this is the shortest method of determining the country of an IP addresss.

The one-liner issues a shell_exec() to the whois command and parses the two letter country code from it.

if(preg_match('/^(\d+\.?){4}$/',$ip)) {
  $country = trim(shell_exec('whois '.$ip.' -H | grep country | awk \'{print $2}\''));
}

The preg_match() is of course optional, but for your own good, because as you may know, shell_exec() is VERY DANGEROUS.

Note: This does not work on all versions of WHOIS!

Also note that there is a daily query limit to the RIPE.NET database, so it's good to cache the results. It's not like that information gets changed often.

Aug 9, 2012

PHP :: session expires after inactivity

If you're getting frustrated why the session expires after 30 minutes, 1 hour of inactivity, you've probably already looked into the problem.

There are a few reasons why the session expires, so let's look at how session actually works.

On the server-side, PHP (usually / by default) stores session variables into files, usually in the /tmp folder.
The script sends a session cookie to the client, which expires after the session closes. At each request, the client sends the PHP session ID cookie back to the server, which uses this ID to access the session variables in the filesystem.
Now, so far we can understand that when the user closes the browser, the session cookie is removed and there is no way of accessing the session variables. The client has closed the session.

A setting in the php.ini file defines how long the session cookie should remain valid.


session.cookie_lifetime = 0

Setting the session.cookie_lifetime to 0 means that the session cookie is valid until the browser is closed. Setting it to something larger than 0 means that it remains valid for so many number of seconds.


Sometimes, however, the user is logged out of the session even though the browser has not been closed and the session cookie still exists. In this case, the server closed the session, meaning it deleted the files containing the session variables. This process is called garbage collection. Garbage collector in PHP deletes old session files that have a timestamp of last access time longer than the defined limit. The setting in php.ini for it is defined in seconds:

session.gc_maxlifetime = 1440

Session files older than 24 minutes [of inactivity] will be deleted. Changing the value to something like 1 week or a month can cause the session to be seemingly permanent. Some browsers have the option to never delete or reset session cookies even after browser restart (in chrome: continue where I left off), so you can achieve permanent sessions without implementing your own system to automatically restart the session via cookies or other types of local storage.

Feb 28, 2012

PHP :: Advice on using count()

I may be a little late realising this, but a few dozen thousand lines of PHP code in, it's better late than never.

Let's look at example #1 on count() manual
http://php.net/manual/en/function.count.php

$result count(null); // $result == 0
$result count(false); // $result == 1


Does this strike you as a bit odd? Why the hell should count(0) or count(false) be equal to 1?

Well strangely enough, that's how it is. So for example, if you wrote a mysql_query() wrapper where you return false if the query fails and if you expect an array where you check the number of items, you find out that if the query fails, the count will return 1, and you might get some unexpected results.

Keeping computer security in mind, it's best to avoid count() when just checking if some data is returned.

Alternatives are empty() if checks if a variable contains something other than 0, '0', null, false, array() or ''.

OR, in your wrapper functions, if the query fails, just return NULL!



Feb 18, 2012

PHP :: sending SMS via najdi.si FREE SMS

You need to make an account on https://id.najdi.si before you can use the code.

Example usage:
$sms = new sms();
$sms->send('090666666','one does not simply send an SMS');

https://github.com/jeancaffou/PHP-Najdi-SMS

Sep 14, 2011

URL shortening - make your own URL shortener

If you ever thought about URL shortening, you've probably already figured out, how it works.

Those random letters and numbers which serve as a key (in hashtable data structures) are basically encoded integers, which are AUTO_INCREMENT values in the database.

Here are the encoding and decoding functions:
const ALLOWED_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

function id2short($integer, $base = self::ALLOWED_CHARS) {
	$out = '';
	$length = strlen($base);
	while($integer > $length-1) {
		$out = $base[fmod($integer, $length)].$out;
		$integer = floor( $integer / $length);
	}
	return $base[$integer].$out;
}

function short2id($string, $base = self::ALLOWED_CHARS) {
	$length = strlen($base);
	$size = strlen($string)-1;
	$string = str_split($string);
	$out = strpos($base, array_pop($string));
	foreach($string as $i=>$char) {
		$out += strpos($base, $char) * pow($length, $size - $i);
	}
	return $out;
}

Full code here.

Jul 28, 2011

Facebook php-sdk 3.0 changes getLoginURL()

Somehow these frequent changes are still poorly documented. Documentation is scattered across forums, changelogs, code comments and docs pages. Never on the single place. This is horrible. I'm sorry Facebook people, but this makes you look incompetent.

The most obvious change is the abandonment of the getSession method. I have also found out that getLoginURL has been changed.
'req_perms' has been changed to 'scope'
'next' has been changed to 'redirect_uri'
'cancel_url' option has been removed. You'll have to find another way. If the user denies access it will be redirected to 'redirect_uri'. The user will also be redirected to 'redirect_uri' if he clicks allow.
You can know if the user denied access by looking at these GET parameters:
[error_reason] => user_denied 
[error] => access_denied 
[error_description] => The user denied your request.
Also worth knowing is that redirect_uri will not work every time on a single page load (without refreshing the page).
Explanation:
The first time the users sees the oauth dialog there will be two options: Allow and Deny. Both buttons will redirect to 'redirect_uri'.
The second time the user sees the oauth dialog, Deny option will be renamed to 'Leave app'. 'redirect_uri' will still work.
The third time and so on, the button 'Leave app' will redirect to facebook.com/home.php

While this may not be entirely precise, it is true that eventually the Deny/Leave app button will not follow the redirect_uri parameter.

Jul 19, 2011

PHP goo.gl url shortener

Here's an implementation of Google's url shortener: goo.gl.
<?
class googl {
 const api = 'https://www.googleapis.com/urlshortener/v1/url';
 private $key = null;
 
 public function __construct($key = null) {
  if(defined('GOOGLE_API_KEY')) {
   $this->setKey(GOOGLE_API_KEY);
  }
  
  if(!is_null($key)) {
   $this->setKey($key);
  }
 }
 
 public function setKey($key) {
  $this->key = $key;
 }
 
 public function s($url) {
  $data = $this->shorten($url);
  return isset($data->id) ? $data->id : $url;
 }
 
 public function shorten($url) {
  $key = '';
  $data = array();
  $data['longUrl'] = $url;
  
  if(!is_null($this->key)) {
   $key = '?key='.$this->key;
  }
  
  return $this->fetch(self::api.$key,$data);
 }
 
 public function expand($url) {
  $key = is_null($this->key) ? '' : "&key={$this->key}";
  return $this->fetch(self::api.'?shortUrl='.urlencode($url)."$key&projection=FULL");
 }
 
 private function fetch($url, $data = array()) {
  $ch = curl_init();
  
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
  
  if(!empty($data)) {
   curl_setopt($ch, CURLOPT_POST, 1);
   curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  }
  
  $r = curl_exec($ch);
  curl_close($ch);
  
  return json_decode($r);
 }
}
?>

Apr 25, 2011

PHP: in_range()

function in_range($num, $min, $max) {
   return ($num >= $min && $num <= $max);
}


Mar 18, 2011

Configuring repcached service on Debian/Ubuntu

Repcached is a slightly modified version of memcached, that supports replication of data between two repcached nodes.

Let's say you've read all about these two and you know the benefits of replication and why this article could be useful to you.

One example of why replicated memcache could be useful are replicated PHP sessions between servers.

If you want to configure that PHP sessions are stored in memcache's memory, you need to edit these settings in /etc/php5/apache2/php.ini:
session.save_handler = memcache
session.save_path = "tcp://IP_OF_REPCACHE_1:11311, tcp://IP_OF_REPCACHE_2:11311"
and these optional settings in /etc/php5/apache2/conf.d /memcache.ini:
memcache.maxratio=0
memcache.allow_failover=1
memcache.allow_failover setting is used if one of the servers becomes unreachable, so there is an automatic failover.
Read more about configuring PHP sessions in memcached here.

Now, let's set up repcached to start on boot and System V init scripts, so you can easly start and stop the daemon with the service command.

Steps described here imitate memcached's default configuration in great detail, so you shoud set up memcached before repcached.
sudo apt-get install memcached

Obtain, configure, compile and install repcached. There is a dependancy with libevent-dev for repcached.
sudo apt-get install libevent-dev
tar xvf memcached-1.2.8-repcached-2.2.tar 
cd memcached-1.2.8-repcached-2.2/
./configure --enable-replication
make
make install

(Read this if it won't compile)

At this point you have two installations of memcached. Default memcached that came from apt packages, which is installed in /usr/bin/memcached and repcached, that installed itself in /usr/local/bin/memcached, leaving the original memcached intact.

Now that we have both versions installed, we can copy memcached's default settings and init script and modify them to use repcached. This way you can quickly switch between versions. I would even recommend using default ports (just remember to firewall them!) Arguments are saved in /etc/memcached.conf, so we will create /etc/repcached.conf

See example here.

Note that the only differences with memcached.conf is the name (repcached) and two extra arguments: -x for the server IP and -X for replication port.

Memcached has an enable/disable config in /etc/default so you can quickly switch between daemons or disable them. We will copy this as well.
cp /etc/default/memcached /etc/default/repcached
vi /etc/default/repcached
Change the line to: ENABLE_REPCACHED=yes, and then edit /etc/default/memcached
vi /etc/default/memcached
and disable it, by changing the line to ENABLE_MEMCACHED=no.

Now let's move on to init scripts.
cd /etc/init.d
cp memcached repcached
Edit the file /etc/init.d/repcached.

Here is my example.

Again, we didn't change much, mostly changed from memcached to repcached, but note that the actual start-up of the service happens in this file: /usr/share/memcached/scripts/start-repcached which doesn't exist yet, so we will copy and edit it.
cp /usr/share/memcached/scripts/start-memcached /usr/share/memcached/scripts/start-repcached
File contents or /usr/share/memcached/scripts/start-repcached

Setting up repcached to start at boot

We need to be sure that /etc/init.d/repcached is executable. If you copied it from memcached, everything should be OK, but if init's not recognising the repcached service, you need to chmod +x /etc/init.d/repcached

After you've run update-rc.d command in the terminal it will create shortcuts in rc?.d files which are read at boot.
update-rc.d repcached defaults
For more information on update-rc.d, click here.

You have successfully configured repcached as a service and to start on boot.

To start/stop repcached use
service repcached start
service repcached stop
Try to run repcached by hand at first with the configuration you provided in /etc/repcached.conf.
In my example it's this:
/usr/local/bin/memcached -m 64 -p 11211 -u memcache -X 11212 -x 22.163.130.33

After installing repcached on another machine I've found out that the default user for memcached is nobody, not memcache, so please always check the differences from the default memcache config with the repcached config you've modified or copied from here.

Feb 5, 2011

PHP:: socket_select(), socket_write() and socket_recv()

As it was already said, some clients need \0 character to end transmission, for example Flash's XMLSocket.

You should also be prepared to read less data than you have requested.

Here is an example of a socket buffer - it's an array which has socket resources for keys and an array of a timestamp and recieved data as values.

I find that the best practice for sending data is trailing it with a new line and zero character (\n\0), because you will probably have different types of clients which behave differently for reading data from sockets. Some need a \n to fire an event, some need \0.

For recieving data, sometimes you will get splitted data - this can hapen because the buffer is full (in my example 8192 bytes) or it just gets broken during transmission in lower levels.

Sometimes you can read two messages at once, but they have a zero character in between, so you can just use preg_split() to split the messages. The second message may not be complete, so you add it to your buffer.

 const message_delimiter = "\n\0";

 /*
  * Clear socket buffers older than 1 hour
  */
 function clear_buffer() {
  foreach($this->buffer as $key=>$val) {
   if(time() - $val['ts'] > 3600) {
    unset($this->buffer[$key]);
   }
  }
 }

 /*
  * Add data to a buffer
  */
 function buffer_add($sock,$data) {
  if(!isset($this->buffer[$sock])) {
   $this->buffer[$sock]['data'] = '';
  }

  $this->buffer[$sock]['data'] .= $data;
  $this->buffer[$sock]['ts'] = time();
 }

 function buffer_get($sock) {
  // split buffer by the end of string
  $lines = preg_split('/\0/',$this->buffer[$sock]['data']);

  // reset buffer to the last line of input
  // if the buffer was sent completely, the last line of input should be
  // an empty string
  $this->buffer[$sock]['data'] = trim($lines[count($lines)-1]);

  if(!empty($this->buffer[$sock]['data'])) {
   debug("buffer is not empty for $sock, len: ".strlen($this->buffer[$sock]['data']));
  }

  // remove the last line of input (incomplete data)
  // parse any complete data
  unset($lines[count($lines)-1]);

  // return only the fully sent data
  return $lines;
 }

 function read(&$sock,$len=8192,$flag=MSG_DONTWAIT) {
  $lines = array();

  $this->clear_buffer();

  $bytes_read = @socket_recv($sock,$read_data,$len,$flag);

  if ($bytes_read === false || $bytes_read == 0) {
   return false;
  } else {
   debug("recv: $read_data");
   $this->buffer_add($sock,$read_data);
   return $this->buffer_get($sock);
  }
 }

 /*
  * Write to a socket
  * add a newline and null character at the end
  * some clients don't read until new line is recieved
  *
  * try to send the rest of the data if it gets truncated
  */
 function write(&$sock,$msg) {
  $msg = $msg.self::message_delimiter;
  $length = strlen($msg);
  while(true) {
   $sent = @socket_write($sock,$msg,$length);
   if($sent <= 0) {
    return false;
   }
   if($sent < $length) {
    $msg = substr($msg, $sent);
    $length -= $sent;
    debug("Message truncated: Resending: $msg");
   } else {
    return true;
   }
  }
  return false;
 }

Dec 29, 2010

PHP:: Facebook getLoginUrl iframe next parameter redirect issue

DUE TO FREQUENT FACEBOOK API CHANGES THIS ARTICLE IS OUTDATED.

The php-sdk from Facebook has some bugs in it.

http://stackoverflow.com/questions/3380876/how-to-authorize-facebook-app-using-redirect-in-canvas http://forum.developers.facebook.net/viewtopic.php?id=70575

These solutions didn't work for me, so I had to change the function getLoginUrl in class Facebook

  public function getLoginUrl($params=array()) {
 $currentUrl = $this->getCurrentUrl();
 $args = array(
        'api_key'         => $this->getAppId(),
        'cancel_url'      => 'http://www.facebook.com/',
        'display'         => 'page',
        'fbconnect'       => 0,
        'next'            => $currentUrl,
        'return_session'  => 1,
        'session_version' => 3,
  'canvas'          => 1,
        'v'               => '1.0',
      );
 foreach($params as $key=>$val) {
  $args[$key] = $val;
 }
 return $this->getUrl(
  'www',
  'login.php',
  $args
 );
  }
Example:
if($me) {
 $logoutUrl = $facebook->getLogoutUrl();
} else {
 $loginUrl = $facebook->getLoginUrl(array('next'=>'http://apps.facebook.com/xxxxxxx/'));
 ?>
 <script type="text/javascript">
 top.location.href = '<?=$loginUrl?>';
 </script>
 <?php 
 exit;
}

Dec 6, 2010

PHP:: relative paths in include() or require()

While this might seem fairly obvious, I'd still like to point out that PHP's functions include(), include_once(), require() and require_once() have problems including files in different directories where paths of the filenames are relative. The absolute path of the included files is generated from the filename of the first included file, so any relative path in the second nested include will not have relative paths starting from it's directory, but the directory of the first file. To avoid confusion I find it's best to use absolute paths with the magic constant __FILE__, which is always the current script's filename.

Example:
require_once(dirname(__FILE__).'/../../include_all.php');

Jan 18, 2010

PHP: calculate distance between two points

Each point is an one-dimensional array, each value in the array is a coordinate, so dimension of coordinates in this function is arbitriary.
 /*
 * Calculates Euclidian distance between two points in p-norm
 * Points $p1 and $p2 are Nx1 matrices
 */
 function euclidian_distance($p1,$p2) {
  if(count($p1) != count($p2)) return false;
  $distances = 0;
  
  $p1 = array_values($p1);
  $p2 = array_values($p2);
  
  $norm = count($p2);
  
  for($i=0;$i<$norm;$i++) $distances += pow(abs($p1[$i]-$p2[$i]),2);
  
  return pow($distances,1/2);
 }

Sep 17, 2009

PHP: mail() Sender domain must exist - UPDATE

Regarding my last post, it seems that you can't set the Return-Path in the header parameter of the mail() function. The workaround is in the fifth argument of mail() - the additional parameters. If you set the "-fsender@domain.com" it should set the Return-Path. Here's my mail() wrapper function:
function construct_mail($to,$subject,$content,$sender='') {
$from = ADMIN_EMAIL;
if(!empty($sender)) $from = $sender;
$header  = "From: $from\r\n";
$header .= "Content-Type: text/html; charset=utf-8\r\n";
$header .= "Date: ".date("r")."\r\n";
$header .= "Reply-To: $from\r\n";
$header .= "Return-Path: $from\r\n";
$header .= "X-Mailer: PHP\r\n";
$content = ''.$content.'';
$subject = ' =?UTF-8?B?'. base64_encode($subject) ."?=";
return mail($to,$subject,$content,$header,"-f$from");
}
Also, the -f switch might trigger a E_WARNING if you don't set the trusted users (the user that executes the script - webserver) in the /etc/mail/trusted-users

UPDATE: http://dev.kafol.net/2013/01/sendmail-x-authentication-warning-user.html