Archivo de la etiqueta: Php

curl_setopt(): CURLOPT FOLLOWLOCATION cannot be activated when an open_basedir is set

[programación]

El mensaje de error completo es este:

Severity: Warning
Message:  curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set
Filename: src/Mandrill.php
Line Number: 68

Este es un problema conocido de la API PHP de Mandrill, y existe un Pull Request con la solución creado en abril de 2014, pero por el motivo que sea, no han añadido la corrección al repositorio principal.

Yo utilizo Composer y Packagist para gestionar las dependencias en mis proyectos, por lo que como solución temporal puedes modificar el fichero Mandrill.php con las correcciones, pero esto significa repetir el mismo proceso en todos los entornos de desarrollo, y es fácil olvidarse de hacerlo cuando han pasado unos meses.

Como solución más robusta, he creado un fork the la librería de Mandrill, y la he subido a Packagist para que pueda ser usada. Es tan sencillo como añadir esto a tus “require” en el composer.json:

"carlos_llongo/mandrill": "dev-master"

O también puedes usar:

composer require "carlos_llongo/mandrill:dev-master"

Instalar MCrypt en Ubuntu

[linux] [programación]

En un artículo anterior ya hable de como instalar MCrypt en Max OSX. En ubuntu el proceso también es sencillo.

1. Instalamos MCrypt:

sudo apt-get install php5-mcrypt

2. Indicamos a PHP que puede usar el módulo. Editamos el fichero php.ini:

sudo gedit /etc/php5/cli/php.ini

3. Al final de la sección de Dynamic Extensions añadimos esta línea y guardamos el fichero:

extension=mcrypt.so

¡Y ya está! Fácil 😉

phpunit con CIUnit no hace nada

[linux] [programación]

Ejecución de phpunit sin salida por consola

Este es otro claro ejemplo de problema en informática al que dedicas un montón de tiempo y te vuelves loco para resolverlo, y al final resulta ser una chorrada. El problema es el siguiente: yo hago mis tests para CodeIgniter utilizando CIUnit y en mi anterior sistema todo funcionaba (MacOSX). Cambio de sistema a un Linux Ubuntu, y cuando lanzo los tests no ocurre nada. No hay mensajes, no hay errores, no hay nada de nada.

Así que tras muchos días de pruebas, en mi desesperación me pongo a añadir mensajes de debug por todo el código de la librería CIUnit y el core de CodeIgniter para encontrar en que momento se para la ejecución, hasta llegar a esta línea:

return @mysql_pconnect($this->hostname, $this->username, $this->password);

Esta línea en el fichero /system/database/drivers/mysql/mysql_driver.php es donde salimos de CodeIgniter para entrar en el core de PHP y es donde la ejecución está fallando. Y a algún “genio” se le ocurrió que sería una buena idea silenciar los errores…

Al quitar el silencio, PHP nos informa de que no puede conectarse a la base de datos porque no tiene el driver de mysql instalado, lo cual podemos solucionar fácilmente con:

sudo apt-get install php5-mysql

Aquí dejo un listado de comandos que me han sido útiles en la resolución del problema:

Comprobar si tienes un módulo instalado:

php -i | grep nombre_del_modulo

Por ejemplo:

php -i | grep mysql

Obtener el listado de módulos que podemos instalar:

apt-cache search php5-

Instalar PHP con MCrypt en Mac OSX Mountain Lion

[programación]

En el servidor de producción no tenía ningún problema, pero al montar el servidor en local para lanzar pruebas con el PHPUnit me saltaron estos errores:

Fatal error: Call to undefined function mcrypt_encrypt

Fatal error: Call to undefined function hex2bin

El primero ocurre porque el XAMPP no trae la librería MCrypt instalada por defecto. El segundo ocurre porque la función hex2bin se incluye en PHP 5.4 y yo estaba trabajando con la 5.3. Por tanto, la solución consiste en instalar PHP 5.4 con MCrypt integrado.

La forma más sencilla de hacer esto es utilizar Homebrew. La documentación para instalarlo está en su web y es tan sencilla como copiar una línea en el terminal. Una vez instalado hacemos lo siguiente:

brew update
brew install php54-mcrypt

A continuación debemos añadir el nuevo PHP a nuestro PATH. Cuando termina la instalación, brew nos dice donde lo ha instalado, pero la ruta será similar a esta: /usr/local/Cellar/php54/5.4.28/

Abrimos el fichero .bash_profile en el directorio de nuestro usuario y añadimos estas dos líneas:

export MCRYPT_PHP=/usr/local/Cellar/php54/5.4.28/bin
export PATH="$MCRYPT_PHP:$PATH"

Reiniciamos el terminal y ya podremos utilizar PHPUnit con el nuevo PHP instalado.

Desencriptar código PHP

[programación]

Últimamente me ha tocado lidiar con código PHP que se encuentra encriptado y ofuscado. Se trata de un módulo para PrestaShop de la empresa Agile. En nuestro caso nos daba una funcionalidad muy próxima a lo deseado, pero menuda sorpresa al intentar corregir algunos elementos y descubrir que el código estaba encriptado y ofuscado.

En este caso el tipo de encriptación utilizado era hexadecimal. Tras buscar un rato, encontré esta página que realiza una desencriptación del código:

DDecode

Funciona bastante bien, pero en un par de casos parece que se queda a mitad fichero y no acaba de realizar la desencriptación. Buscando un poco más encontré en Stackoverflow este tema donde explican como desencriptar el hexadecimal utilizando JavaScript. Lo he probado y también funciona bastante bien, al menos este termina de desencriptar el fichero. Así que mi solución ha sido utilizar ambos métodos y comparar el código resultante para reconstruir el fichero original.

Por si a alguien re puede resultar este segundo método, he creado una sencilla página donde puede insertar el código para ser desencriptado. La encontraréis en:

Just Another Hex Decoder!

phpThumb no funciona con enlaces simbólicos

[linux] [programación]

Al menos eso era aparentemente mi problema, y mi punto de partida en búsqueda de una solución.

Básicamente el problema se podía resumir de la siguiente manera:

La imagen se ve correctamente:

http://www.midominio.com/imagenes/test.jpg

La imagen se ve correctamente y redimensionada:

http://www.midominio.com/phpThumb.php?url=imagenes/test.jpg&h=150

La imagen se ve correctamente. La carpeta “colecciones” es un enlace simbólico:

http://www.midominio.com/colecciones/imagenes/test.jpg

La imagen no se ve y phpThumb muestra un mensaje sobre el correcto uso del plugin:

http://www.midominio.com/phpThumb.php?url=colecciones/imagenes/test.jpg&h=150

Da la impresión que el problema aquí es ese enlace simbólico, pero no, este comportamiento se debe al siguiente valor de configuración de phpThumb:

$PHPTHUMB_CONFIG['allow_src_above_docroot'] = false;

En mi caso el enlace simbólico estaba apuntando a un directorio que estaba a un nivel superior que el contenido de la web, lo cual unido a este parámetro estaba bloqueando su acceso. La solución era tan simple como ponerlo a ‘true’.

La solución la encontré en una de las últimas respuestas de este hilo.

Tutorial de Flexi auth #1 – Primeros pasos

[programación]

Gracias a un amigo, he descubierto CodeIgniter. Es un framework de PHP para desarrollar aplicaciones rápidamente y la verdad es que es una pasada. Según va cogiendo fuerza, van saliendo extensiones para CodeIgniter, y una de ellas es Flexi auth, el cual añade toda la funcionalidad para autenticación y permisos de usuario. El problema es que a diferencia de CodeIgniter, que tiene muy buena documentación paso a paso de como hacer las cosas, la documentación de Flexi auth es muy pobre. Tiene un buen tutorial de instalación y después te dicen “Mira, ahí tienes un ejemplo de las principales funcionalidades. Mírate el código”. La demo está muy completa, y ese es su fallo, tiene demasiada información para alguien que lo único que quiere es empezar poco a poco.

Así pues, voy a poner aquí lo imprescindible para tener la funcionalidad más básica del Flexi auth, que es hacer Login y Logout.

En mi sencillo ejemplo uso un único controlador, que es pages.php, siguiendo el ejemplo del tutorial de CodeIgniter. Solo voy a ir poniendo código que no apareciera en dicho tutorial.

El constructor de la clase Pages sería el siguiente:

public function __construct() {
    parent::__construct();
    $this->auth = new stdClass;
    $this->load->library('flexi_auth');
    $this->load->helper('url');
}

La creación del objeto auth es imprescindible para el funcionamiento de la librería Flexi auth, y después cargamos la librería extendida que nos dará la funcionalidad para el Login/Logout. El ayudante para URLs es útil a la hora de componer direcciones en las vistas, así como para las redirecciones.

public function login() {
    $this->load->helper('form');

    $this->load->library('form_validation');

    // Set validation rules.
    $this->form_validation->set_rules('login_identity', 'Usuario', 'required');
    $this->form_validation->set_rules('login_password', 'Contraseña', 'required');

    // Run the validation.
    if ($this->form_validation->run()) {
        // Verify login data.
        $this->flexi_auth->login($this->input->post('login_identity'), $this->input->post('login_password'));

        // Save any public status or error messages
        // (Whilst suppressing any admin messages) to CI's flash session data.
        $this->session->set_flashdata('message', $this->flexi_auth->get_messages());

        // Reload page, if login was successful, sessions will
        // have been created that will then further redirect verified users.
        redirect('home');
    } else {
        // Set validation errors.
        $this->data['message'] = validation_errors('<p class="error_msg">', '</p>');

        return FALSE;
    }
}

Esto está íntegramente sacado del código de la demo del Flexi auth, pero eliminando todo lo que no vamos a necesitar. Al final, solo dejamos primero la validación del formulario de Login, y si todo está correcto, intentamos hacer Login. Si hay éxito hacemos una redirección a la página principal, y en caso contrario, podemos mostrar un mensaje al usuario.

public function logout() {
    // By setting the logout functions argument as 'TRUE', all browser sessions are logged out.
    $this->flexi_auth->logout(TRUE);

    // Set a message to the CI flashdata so that it is available after the page redirect.
    $this->session->set_flashdata('message', $this->flexi_auth->get_messages());

    redirect('home');
}

Más sencillo imposible. Creo que el código es bastante autoexplicativo. Vamos a por el formulario de Login. Este está en una vista llamada login.php:

<h2>Login</h2>
<?php echo validation_errors(); ?>
<?php echo form_open('login/login');?>

    <label for="identity">Usuario</label>
    <input type="input" id="identity" name="login_identity" /><br />
    <label for="password">Contrase&ntilde;a</label>
    <input type="password" id="password" name="login_password" /><br />
    <input type="submit" name="login_user" id="submit" value="Login" />

</form>

Dos campos de texto (nombre de usuario y contraseña) y un botón. Importante que los nombres sean los mismos que tenemos en el método de Login.

También he introducido un poco de código en la cabecera de las páginas para que se muestre la información del estado del usuario (si está logueado o no):

<p>
    <?php
    if ($this->flexi_auth->is_logged_in()) {
    ?>
        Usuario: <?php echo $this->flexi_auth->get_user_identity(); ?>
        &nbsp;|&nbsp;<a href="<?php echo base_url('logout'); ?>">Salir</a>
    <?php
    } else {
        echo 'Usuario no registrado';
    }
    ?>
</p>

Simplemente compruebo si el usuario está logueado, en cuyo caso muestro su identificación y un enlace de Logout.

Por último falta añadir un par de rutas a routes.php:

$route['login/login'] = 'pages/login';
$route['logout'] = 'pages/logout';

Y con eso ya debería funcionar. En el próximo post haré el proceso de registro.

SPAM Fight – Round 4!

[blog] [programación]

Cuando pensaba que había encontrado el método definitivo anti-spam, resultó no ser eficaz ni durante un día entero. Algunos de los comentarios se estaban marcando correctamente como SPAM, que deben ser los que atacan directamente a wp-comments-post.php. Sin embargo seguían llegando algunos comentarios de SPAM, y no tengo ni idea de como lo están haciendo. Debe haber otra manera de hacer un POST de un comentario, pero por el momento no la he encontrado.

Lo que sí que me he dado cuenta, es que estos comentarios nunca tienen establecido un correo electrónico (al menos de momento). Utilizando ese dato, he complementado el método de identificación de SPAM con el requerimiento de un correo. En wp-includes/comment.php:

if(strpos($comment_content, '[THIS#IS#SPAAAM!]') !== false
    || !is_email($comment_author_email)){
    $comment_approved = 'spam';
}

De momento con esto estoy cazando todo el SPAM. ¡Veremos si dura!

El retorno del SPAM

[blog] [programación]

La solución con la base de datos de SPAM debo reconocer que fue ingeniosa. Está muy en mi línea de “matar moscas a cañonazos”. El problema es que lo que se dice eficaz, no era. Digamos que el número de direcciones IP desde las que puede llegar SPAM, o el número de direcciones de correo o URLs que pueden usar en los comentarios están en un orden de magnitud tal que nunca voy a ser capaz de filtrar los comentarios basura.

Todo vuelve a la pregunta que me hice hace ya hace 9 meses: ¿Como estaban los bots de SPAM enviando comentarios saltándose el formulario de envío? Y por pura casualidad, encontré a que página están atacando. Probablemente están haciendo un POST directamente contra wp-comments-post.php.

Así que volvemos a la sencillez. ¿Como detectar un POST que llega a wp-comments-post.php sin pasar por comments.php? Fácil. Si los bots están lanzando un POST genérico para blogs en WordPress, añadamos un nuevo campo al formulario. Si en wp-comments-post.php detectamos que ese campo no está establecido, entonces se está saltando comments.php y debe ser SPAM. Está solución es tan eficaz que hace que mis dos soluciones anteriores queden obsoletas, así que para aligerar la página y reducir tiempo de procesado, elimino las modificaciones del código anteriores. El código definitivo quedaría de la siguiente manera:

Dentro de los ficheros del tema, en el fichero comments.php:

Añadimos un nuevo campo oculto al formulario:

<input type="hidden" id="comment_post_antirobot" name="comment_post_antirobot" value="0" />

Por si acaso el robot de SPAM está comprobando que campos tiene el formulario, cambiamos su valor por javascript:

<script type="text/javascript">
  document.getElementById('comment_post_antirobot').value = "1";
</script>

En wp-comments-post.php:

Obtenemos el valor del campo oculto, y en caso de no estar establecido o no ser igual a 1, marcamos el comentario como SPAM modificando el contenido:

$comment_antirobot    = ( isset($_POST['comment_post_antirobot']) ) ? trim($_POST['comment_post_antirobot']) : null;

if( empty($comment_antirobot) || $comment_antirobot == '0'){
    // It's SPAM
    $comment_content = '[THIS#IS#SPAAAM!] '.$comment_content;    
}

En wp-includes/comment.php, en el método wp_insert_comment:

Comprobamos si contiene la marca de SPAM en su contenido, y en ese caso lo etiquetamos como SPAM:

if(strpos($comment_content, '[THIS#IS#SPAAAM!]') !== false){
    $comment_approved = 'spam';
}

De momento, el SPAM que está llegando se está etiquetando todo automáticamente como tal. A ver cuanto dura la solución… aunque en esta tengo bastante confianza!

El SPAM contraataca!

[blog] [programación]

El método que implementé contra el SPAM, que comenté en esté articulo, ha estado funcionando bien, pero en los últimos 2 meses ha dejado de cazar todo el SPAM. Los últimos comentarios que están llegando sí que  traen el campo de email cumplimentado, por lo que ya no es efectivo. Dándole vueltas al asunto, se me ha ocurrido hacer mi propia base de datos anti-spam. La idea es que cada vez que llega un nuevo comentario, se comprueba con la base de datos si es SPAM, y en caso que lo sea, añadir sus datos para futuras comprobaciones. El código es el siguiente:

En wp_includes/comment.php en el método wp_insert_comment, justo antes de insertar el comentario hago esta comprobación:

if(!is_email($comment_author_email)){
    $comment_approved = 'spam';
}
else{
    $spam_query = "SELECT * FROM spam_data "
    ."WHERE data = '".$comment_author_IP
    ."' OR data = '".$comment_author_email
    ."' OR data = '".$comment_author_url."'";

    /*$spam_query = "SELECT * FROM spam_data "
    ."WHERE 1";*/

    $spam_results = $wpdb->get_results($spam_query);

    /*if(count($spam_results) <= 0){ // It's SPAM
        $comment_approved = 'spam';
    }*/

    if(count($spam_results) > 0){ // It's SPAM
        $comment_content .= " count: ".count($spam_results);
        $comment_approved = 'spam';

        insert_into_spam_data($comment_author_IP);
        insert_into_spam_data($comment_author_email);
        insert_into_spam_data($comment_author_url);
    }    
}

Y para añadir los datos a la BD he añadido este método:

function insert_into_spam_data($spam_data){
    global $wpdb;

    $spam_query = "SELECT * FROM spam_data "
    ."WHERE data = '".$spam_data."'";

    $spam_results = $wpdb->get_results($spam_query);

    if(count($spam_results) <= 0){
        $wpdb->insert(
            'spam_data',
            array(
                'data' => $spam_data
            ),
            array(
                '%s'
            )
        );
    }
}

Esto debería parar una buena parte del SPAM. El siguiente paso será que cuando yo marque manualmente un comentario como SPAM, sus datos también sean incluidos en la tabla con la información de SPAM. Para ello en el metodo wp_spam_comment, tras marcar el comentario como SPAM, añadimos este código:

insert_into_spam_data($comment->comment_author_IP);
insert_into_spam_data($comment->comment_author_email);
insert_into_spam_data($comment->comment_author_url);

Ahora faltará ver como de efectivas son las medidas. Me estoy planteando introducir algún otro tipo de comprobación anti-spam, viendo que el captcha no está funcionando. Pero eso para otro artículo más adelante 🙂