jeudi 17 novembre 2011

Cross-site Ajax proxy version curl

La technique Ajax permet de modifier partiellement une page affichée par le navigateur pour la mettre à jour sans avoir à recharger cette dernière dans son intégralité. Elle dépend de XMLHttpRequest, objet intégré coté client utilisable en Javascript.

Il existe une multitude de tutoriels décrivant le fonctionnement et techniques.

Pour ce post, je m'intéresserai principalement à une limitation de la majorité des navigateurs actuels, due à des raisons de sécurité, concernant l'utilisation de domains différents.
En effet, une script JS lancé sur une page www.name.com ne peut normalement pas diriger d'objet vers un domain différent.

Là où cela devient vraiment embêtant, c'est que les subdomains sont égalements limités : impossible d'utiliser, par exemple, l'adresse ww1.name.com pour ces mêmes fonctions.
Dans le cas où vous devriez lancer des scripts sur différents serveurs, comme pour l'envoi d'images à un serveur Master, depuis un serveur Slave (Post à venir), le navigateur bloquerait ces communications.

Alors comment faire ?

La solution s'appelle le "Cross-site Ajax".
L'idée est de créer un fichier PHP fonctionnant comme un proxy et dont nous allons nous servir en appel Ajax pour communiquer avec les différents domains/subdomains.

Je vous propose donc un fichier permettant d'outrepasser les protections navigateurs, basé sur l'extension cURL (librairie libcurl, voir post Plus rapide que file_get_contents : optez pour cURL !) :

Contenu du fichier : csaproxy.php

 <?php
header('Content-type: text/html; charset=iso-8859-1');


if ( $_GET['pass'] != "abcde12345" ) die(); //protection par mot de passe lors de l'appel



function curl_http_request(
    $url = 'www.domain.com/index.php',         /* Target URL */
    $getdata = array(),        /* HTTP GET Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $postdata = array(),       /* HTTP POST Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $filesdata = array(),       /* HTTP FILES Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $cookie = array()         /* HTTP Cookie Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    ) {

    $curlopt = array();
    $cookie_str = '';
    $getdata_str = count($getdata) ? '?' : '';

    foreach ($getdata as $k => $v) $getdata_str .= (strlen($getdata_str)>0?'&':'').urlencode($k).'='.urlencode($v);

   //assignation uploads en POST : _POST['champ']=filetmpname et _POST['champ_real']=filename
    foreach ($filesdata as $k => $v) {
        if ( $filesdata[$k]['error'] == UPLOAD_ERR_OK ) {
            $postdata[$k]='@'.$filesdata[$k]['tmp_name'];
            $postdata[$k."_real"]=$filesdata[$k]['name'];
        }
    }
   
    foreach ($cookie as $k => $v) $cookie_str .= (strlen($cookie_str)>0?'; ':'').urlencode($k).'='.urlencode($v);

    if ( count($postdata) ) {
        $curlopt = array (
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => $postdata
        );
    }

    $default_curlopt = array(
    CURLOPT_TIMEOUT => 2, //timeout 2sec
    CURLOPT_FAILONERROR => 1, //si page error, on ne d/l pas
    CURLOPT_RETURNTRANSFER => 1, //return le transfert as a string
    CURLOPT_FOLLOWLOCATION => 1, //on follow si redirection header desactive par défaut
    CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT']?$_SERVER['HTTP_USER_AGENT']:"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.13) Gecko/20101203 AlexaToolbar/alxf-1.54 Firefox/3.6.13 GTB7.1"
    );
    $curlopt = array(CURLOPT_URL => $url.$getdata_str) + $curlopt + $default_curlopt;
   
   
    // Création d'une nouvelle ressource cURL
    $ch = curl_init();
   
    // Configuration de l'URL et d'autres options
    curl_setopt_array($ch, $curlopt);
   
    //Envoi des données
    $results_returned = curl_exec($ch);
    if ( $results_returned === false ) trigger_error(curl_error($ch));

    // Fermeture de la session cURL
    curl_close($ch);
   
    return $results_returned;
}


//destinations autorisées, en double pour redoubler de vigilance
$accepted_file_proxycsa=array(
    '/script.php',
    '/script.php' => 'www.domain.com',
    '/main/postit.php',
    '/main/postit.php' => 'sub.domain.com'
);

if ( in_array($_GET['filename_csa'],$accepted_file_proxycsa) ) {
    $target_filename=$_GET['filename_csa'];
    $target_site=$accepted_file_proxycsa[$_GET['filename_csa']];
}

if (( strlen($target_filename) > 0 ) && ( strlen($target_site) > 0 )) {
    echo curl_http_request($target_site.$target_filename,$_GET,$_POST,$_FILES,$_COOKIE);
}

?>

Une fois ce fichier uploadé sur votre serveur, vous pouvez désormais appeler tout fichier présent dans n'importe quel domain/subdomain.
Ne pas oublier d'inclure les destinations autorisées dans le array $accepted_file_proxycsa. Vous pouvez également vous affranchir de ce passage et rediriger directement l'url contenu dans le $_GET.


Exemple de code Javascript incluant le cross-site Ajax :
<script type="text/javascript">

function csajax(CHOIX,idcible) {   
   
    var xhr_object = new Array;
    xhr_object[CHOIX] = null;
    if(window.XMLHttpRequest) // Firefox
        xhr_object[CHOIX] = new XMLHttpRequest();  
    else if(window.ActiveXObject) // Internet Explorer  
        xhr_object[CHOIX] = new ActiveXObject("Microsoft.XMLHTTP");  
    else { // XMLHttpRequest non supporté par le navigateur  
        alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest...");  
        return;
    }


   xhr_object[CHOIX].open("POST", "http://<?php echo $_SERVER['SERVER_NAME']; ?>/csaprx.php?filename_csa=/main/postit.php", true);  


    xhr_object[CHOIX].onreadystatechange = function() {
        if ( CHOIX == "postit" ) {
            if (( xhr_object[CHOIX].readyState == 4) && ( xhr_object[CHOIX].responseText.length > 0 )) {
                document.getElementById(idcible).innerHTML=xhr_object[CHOIX].responseText;
                xhr_object[CHOIX].abort;
             }
        }
     };

    xhr_object[CHOIX].setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=iso-8859-1");

    if ( CHOIX == "postit" ) {
        var data = "message="+encodeURIComponent(document.getElementById('champ_msg').value)+'&auteur='+encodeURIComponent(document.getElementById('champ_auteur').value);

        xhr_object[CHOIX].send(data);
    }
}
</script>

Formulaire HTML correspondant:
<input type="text" name="champ_msg">
<input type="button" value="Envoyer" onclick="csajax('postit','reponse');">
<div id="reponse"></div>

Cet exemple enverra le contenu du champ_msg à http://sub.domain.com/main/postit.php et retournera le résultat dans le div reponse.
C'est aussi simple que ça !

Protection injection MySQL et failles XSS

Ces derniers temps, nous pouvons assister à un déluge d'affaires de piratages, assez médiatisés car touchant plusieurs millions de clients (Sony, Steam, etc). Plus récemment, nous pouvons également parler du site de l'UMP dont une simple faille de type "SQL injection" a permis aux hackers de publier une liste complète de 1000 cadres.

Il faut savoir que ce type d'attaque est très basique et de ce fait, très répandu.
On ne peut concevoir un site internet impliquant l'appel d'une base de données par l'intermédiaire de formulaires, sans penser à sécuriser les informations entrantes qui seront utilisées dans les requêtes.

Alors ? Des sites internet ou plateformes créés un peu trop vite ? Oublis, inattentions ou incompétence des stagiaires développeurs ?

Quoi qu'il en soit, pour ce post, je vous propose un bout de script PHP destiné à sécuriser la récupération de données afin d'éviter les attaques de type "SQL injection" et failles "XSS". La fonction vérifie le type en analysant la chaine contenue dans la variable en utilisant des regexp et ne retourne que ce qui est conforme au pattern.

<?php

//réplique de la fonction mysql_escape permettant de s'affranchir d'une connexion MySQL ouverte (voir php.net)
 function mysql_escape_mimic($inp) {
    if (is_array($inp)) return array_map(__METHOD__, $inp);
    if(!empty($inp) && is_string($inp)) return str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $inp);
    return $inp;
}

//fonction permettant de filtrer les types int, float ou string
function mysql_varclean($text,$dest=null) {
    if ( $dest == "var_int" ) { preg_match("/(-?[0-9]+)/i",$text,$matches); return $matches[1]?$matches[1]:0; }
    elseif ( $dest == "var_float" ) { preg_match("/(-?[0-9]+[.]?[0-9]*)/i",$text,$matches); return $matches[1]?$matches[1]:0; }
    elseif ( $dest == "var_string" ) return trim(mysql_escape_mimic(htmlentities($text)));
    else return trim(mysql_escape_mimic(htmlentities($text)));
}


?>


L'envoi d'une requête MySQL de lecture, modification ou suppression s'effectue alors de la manière suivante :

<?php

$rech_auteur=strlen($_GET['auteur'])>0?$_GET['auteur']$_POST['auteur']; //récupère la donnée "auteur" en priorité sur le GET sinon POST

mysql_connect("localhost", "mysql_user", "mysql_password") or die("Impossible de se connecter : " . mysql_error()); //connexion à la bdd Mysql
mysql_select_db("mydb"); //choix de la base

$result = mysql_query("SELECT `titre`, `annee` FROM `mytable` WHERE `auteur` = ".mysql_varclean($rech_auteur,"var_int")); //envoi de la requête sécurisée

while ($data = mysql_fetch_array($result)) {
   echo "Titre: ".$data['titre']." - année : ".$data['annee']." <br />";
}

mysql_free_result($result
);

?>

Principe de précaution (paranoïa oblige), je vous conseille d'appliquer la fonction à toutes les variables PHP incluses directement dans chacune de vos requêtes MySQL. A vous d'adapter le bon type (int, float ou string) aux variables. De cette façon, vous ne risquez aucun oubli.

Remarque : en ce qui concerne les variables de type string (sujettes aux attaques XSS), j'ai volontairement choisi d'appliquer un htmlentities() afin d'enregistrer des données saines directement dans la base de donnée MySQL. Logiquement, il aurait plutôt fallu enregistrer les données en brut puis appliquer le htmlentities() uniquement par la suite, à l'affichage, car :
- htmlentities convertit certains caractères en entité HTML et rend donc impossible l'exploitation de la base de données pour des recherches de mots clés (notamment accentués) ;
- la conversion en entité HTML peut augmenter considérablement la taille de la variable et par conséquent la taille de la table de votre base de donnée.

Plus rapide que file_get_contents : optez pour cURL !


Lors de la réalisation et mise au point de robots en PHP (bot crawlers notamment), je me suis aperçu que les durées d'exécution du script étaient énormes. Il s'agissait en fait de la fonction file_get_contents() qui met énormément de temps à se connecter/restituer le résultat de la page appelée.

Afin de résoudre le problème, il existe une librairie appelée libcurl contenant une extension : cURL. Cette extension *magique* est 2 à 3 fois plus rapide que les file_get_contents() et fopen() et beaucoup plus facile à utiliser que les fonctions de socket PHP.

Comment l'utiliser ?

Voici un exemple de fonction PHP :
<?php

function curl_http_request(
    $url = 'www.domain.com/index.php',         /* Target URL */
    $getdata = array(),        /* HTTP GET Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $postdata = array(),       /* HTTP POST Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $filesdata = array(),       /* HTTP FILES Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $cookie = array()         /* HTTP Cookie Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    ) {

    $curlopt = array();
    $cookie_str = '';
    $getdata_str = count($getdata) ? '?' : '';

    foreach ($getdata as $k => $v) $getdata_str .= (strlen($getdata_str)>0?'&':'').urlencode($k).'='.urlencode($v);

   //assignation uploads en POST : _POST['champ']=filetmpname et _POST['champ_real']=filename
    foreach ($filesdata as $k => $v) {
        if ( $filesdata[$k]['error'] == UPLOAD_ERR_OK ) {
            $postdata[$k]='@'.$filesdata[$k]['tmp_name'];
            $postdata[$k."_real"]=$filesdata[$k]['name'];
        }
    }
   
    foreach ($cookie as $k => $v) $cookie_str .= (strlen($cookie_str)>0?'; ':'').urlencode($k).'='.urlencode($v);

    if ( count($postdata) ) {
        $curlopt = array (
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => $postdata
        );
    }

    $default_curlopt = array(
    CURLOPT_TIMEOUT => 2, //timeout 2sec
    CURLOPT_FAILONERROR => 1, //si page error, on ne d/l pas
    CURLOPT_RETURNTRANSFER => 1, //return le transfert as a string
    CURLOPT_FOLLOWLOCATION => 1, //on follow si redirection header desactive par défaut
    CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT']?$_SERVER['HTTP_USER_AGENT']:"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.13) Gecko/20101203 AlexaToolbar/alxf-1.54 Firefox/3.6.13 GTB7.1"
    );
    $curlopt = array(CURLOPT_URL => $url.$getdata_str) + $curlopt + $default_curlopt;
   
   
    // Création d'une nouvelle ressource cURL
    $ch = curl_init();
   
    // Configuration de l'URL et d'autres options
    curl_setopt_array($ch, $curlopt);
   
    //Envoi des données
    $results_returned = curl_exec($ch);
    if ( $results_returned === false ) trigger_error(curl_error($ch));

    // Fermeture de la session cURL
    curl_close($ch);
   
    return $results_returned;
}

//exemple: fishing du fichier test.php, utilisant tous les paramètres de formulaire
echo curl_http_request("www.domain.com/test.php",$_GET,$_POST, $_FILES, $_COOKIE);
?>

Facile, accessible et supporte de nombreux protocoles.

Backup automatique de serveur lamp (debian)

En louant des serveurs dédiés lowcost de type Kimsufi (OVH) ou Dedibox (Online.net/Free), la plus grande source de stress (pour ma part) est que le serveur devienne inaccessible, tombe en panne, ou qu'il y ait carrément pertes de données... l'offre de service étant restreinte au minimum, il faut faire avec les moyens du bord.

Si votre serveur est sous debian, je vous propose donc un petit script shell à intégrer, qui:
  • passe votre site internet "en maintenance" (remplacement de fichiers .htaccess) ;
    • nécessaire: à la racine de votre site /var/www/
      • .htaccess
      • .htaccess_maintenance (redirigeant vers une page maintenance.htm)
  • effectue une maintenance de toutes les tables MySQL (optimize/repair/etc.) ;
  • archive et compresse le système sous un nom de fichier daté (format TGZ) ;
  • transfère l'archive vers votre serveur FTP tiers, dans des répertoires classés par date ;
    • nécessaire: package "yafc" (client FTP)
  • supprime les archives vieilles de plus de 30 jours (à régler en fonction de la capacité de votre FTP) ;
    • nécessaire: tâche cron quotidienne (sinon, redéfinir un nombre de jour facteur de la fréquence de la tâche cron)
  • vous envoie un email de notification au démarrage et à la fin d'exécution du script ;
Voici le contenu du script "/cron/backup.sh" à ajuster selon votre configuration :


#file: /cron/backup.sh

#source: http://y-thot.blogspot.com/

#Email de notif
email_notif="adresse@email.com"

#Configuration du FTP
ftp_login="login"
ftp_password="password"
ftp_host="adresse"

phpscript_optimisation="/var/www/maintenance_sql.php password all all"


echo 'Demarrage : ' `date`
echo "Backup commencé à " `date` | mail -s "Backup report" $email_notif

#Configurer les différents fichiers .htaccess pour la redirection vers une page maintenance.htm
echo 'Setting site to : Maintenance (htaccess moves)'
mv /var/www/.htaccess /var/www/.htaccess_moved
mv /var/www/.htaccess_maintenance /var/www/.htaccess

#Date du jour
NOW="$(date +"%d-%m-%Y")"

# Emplacement du dossier de backup local
Dir_backup_local="/ftp_backup"
[ ! -d "/var"$Dir_backup_local/ ] && mkdir -p "/var"$Dir_backup_local/ || :

echo "Optimisations des tables"
php $phpscript_optimisation

echo "Fermeture du service mysql"
service mysql stop

File_db="/var"$Dir_backup_local"/backup.$NOW.tgz"
echo "Enregistrement/compression de / :" $File_db
tar -czf $File_db / --exclude="/var"$Dir_backup_local --exclude=/proc --exclude=/dev --exclude=/sys --exclude=/mnt


#nbre de jour de backup a conserver
nbjours_backup=30
j_a_delete=$(date +%d-%m-%Y --date "$nbjours_backup days ago")

## Conf des rep de backup pour le ftp
Dir_backup_ftp="/backup/"


echo "Transfert au FTP"
## Upload sur le ftp
yafc $ftp_login:$ftp_password@$ftp_host <<EOF
mkdir $Dir_backup_ftp
cd $Dir_backup_ftp
mkdir $NOW
cd $NOW
lcd "/var"$Dir_backup_local/
put -nr *
cd ..
rm -r $j_a_delete
bye
EOF

if [ "$Dir_backup_local" != "" ]; then
cd /var/
echo "Suppression des backup locaux"
rm -r "/var"$Dir_backup_local/
fi

echo "Démarrage de mysql"
service mysql start

echo "ReOptimisations des tables"
php $phpscript_optimisation

echo 'Setting site to : Production (htaccess moves)'
mv /var/www/.htaccess /var/www/.htaccess_maintenance
mv /var/www/.htaccess_moved /var/www/.htaccess

echo 'Fin : ' `date`


echo "Backup terminé à " `date` | mail -s "Backup report" $email_notif


Une fois, le fichier créé, n'oubliez pas de lui donner les droits d'exécution en entrant dans la console :

root@ksXXXXXX:/# chmod u+x /cron/backup.sh

Au passage, vérifiez que votre script fonctionne.
Si tout est ok, il ne reste alors, plus qu'à configurer une nouvelle tache cron pour lancer quotidiennement le script. Pour se faire, lancez le programme de configuration des tâches cron:

root@ksXXXXXX:/# crontab -e

Puis ajoutez la ligne suivante (par exemple) afin de lancer l'opération de maintenance tous les jours à 05h10 du matin avec création de log :

10 05 * * * /cron/backup.sh >> /cron/backup.log

C'est fini !

Afin d'éviter de vilaines erreurs, je vous invite à ne pas recopier directement tout le script mais à l'étudier et l'adapter en fonction de vos besoins et de votre configuration.
Et ne lésinez pas sur les tests !


Maintenance des tables d'une base de données MySQL




Les tables d'une base de données MySQL impliquent nécessairement une fragmentation des fichiers à l'utilisation (suppressions/ajouts d'entrées, ou modifications de champs dynamique VARCHAR, BLOB ou TEXT). 
Les lignes effacées sont, par exemple, conservées dans une liste et les prochaines opérations d'INSERT réutilisent les vieilles positions de lignes, entraînant des espaces de "vide" dans les fichiers de table.
Plus les tables sont fragmentées et plus les temps d'accès aux informations peuvent en être impactés.
Afin de récupérer ces espaces et d'optimiser le contenu des tables, il est nécessaire d'effectuer des opérations de maintenance, en fonction de leur fragmentation.

Pour se faire, il existe plusieurs solutions, dont l'utilisation de l'interface phpMyAdmin qui propose, depuis la fenêtre de sélection des tables, d'effectuer ces opérations de maintenance. Cette solution implique évidemment d'avoir, au préalable, installé l'outil sur votre serveur.

Autrement, je vous propose un petit script PHP très simple vous permettant de lancer des opérations de maintenance de base (analyze, check... optimize) sur vos tables de manière automatique (ne s'applique qu'aux moteurs de table MyISAM).



<?php
/*
Syntaxe:
web: http://votre-url/script.php?pass=password&action=idaction [&option=all]
shell: php script.php password idaction [all]


idaction={
  • "1" ou "analyze" : (analyse et stocke les clés de distribution)
  • "2" ou "check" : (vérifie l'intégrité des tables)
  • "3" ou "checksum" : (calcule la somme de contrôle des tables)
  • "4" ou "repair" (répare les tables corrompues)
  • "5" ou "optimize" (défragmente, trie et met à jour les stats)
  • "all" : effectue toutes les opérations
}

l'ajout du paramètre supplémentaire "all" permet de forcer l'opération sur toutes les tables (sinon uniquement les tables avec perte).

Source: http://y-thot.blogspot.com/

*/

//variables à définir
$script_password="password";


$mysql_host="localhost";
$mysql_user="root";
$mysql_pass="";
$mysql_bdd="votrebasededonnee"; //base de donnée contenant les tables

echo "Script d'optimisation des tables: lancement $mode_cli";

if ( $pass != $script_password ) die("Done. $mode_cli"); //message de quit à personnaliser

if (is_array($argv)) { //accès par shell php
//cli: "php /etc/www/optimisealltables.php pass action option"
    if ($argv[1]) $pass = $argv[1];
    else $pass="";
    if ($argv[2]) $action = $argv[2];
    else $action="5";
    if ($argv[3] == "all" ) $query = "SHOW TABLE STATUS";
    else $query = "SHOW TABLE STATUS WHERE Data_free >0";
   
    $mode_cli="\n";
}
else { //accès par apache
    if ( $_GET['pass'] ) $pass = $_GET['pass'];
    else $pass = "";
    if ( $_GET['action'] ) $action=$_GET['action'];
    else $action = "5";
    if ( $_GET['option'] == "all" ) $query = "SHOW TABLE STATUS";
    else $query = "SHOW TABLE STATUS WHERE Data_free >0";
    $mode_cli="<br />";
   ini_set ('max_execution_time', 60*30); //30 minutes max d'exécution du script, mettre à 0 si pas de limite
}
  
if ( mysql_connect($mysql_host,$mysql_user,$mysql_pass) ) echo "Connexion à la base $mode_cli";
else die("Echec de la connexion");

if ( mysql_select_db($mysql_bdd) ) echo "Sélection de la bdd $mode_cli";
else die("Echec du choix de la bdd");

$result = mysql_query($query) or die("error query");
while($line = mysql_fetch_array($result)) {
    if ( $line[1] != "MyISAM" ) continue;
   
    if (( $action == "all" ) || ( $action == "analyze" ) || ( $action == "1" )) {
        echo "Analyze de la table ".$line[0]." : ";
        mysql_query("ANALYZE TABLE `".$line[0]."`"); //analyse et stocke les clés de distribution
    }
    if (( $action == "all" ) || ( $action == "check" ) || ( $action == "2" )) {
        echo "Check de la table ".$line[0]." : ";
        mysql_query("CHECK TABLE `".$line[0]."` EXTENDED"); //verifie intégrité des tables
    }
    if (( $action == "all" ) || ( $action == "checksum" ) || ( $action == "3" )) {
        echo "Checksum de la table ".$line[0]." : ";
        mysql_query("CHECKSUM TABLE `".$line[0]."` EXTENDED"); //calcule la somme de contrôle des tables
    }
    if (( $action == "all" ) || ( $action == "repair" ) || ( $action == "4" )) {
        echo "Repair de la table ".$line[0]." : ";
        mysql_query("REPAIR TABLE `".$line[0]."` EXTENDED"); //répare les tables corrompues
    }
    if (( $action == "all" ) || ( $action == "optimize" ) || ( $action == "5" )) {
        echo "Optimisation de la table ".$line[0]." : "; //défragmente, trie et met à jour les stats
        mysql_query("OPTIMIZE TABLE `".$line[0]."`");
    }

    echo "effectué. $mode_cli";
}

echo "Script d'optimisation des tables: terminé $mode_cli";
?>


La récupération des variables d'entrée permet d'utiliser le script soit à distance (méthode GET, Apache installé), soit de lancer directement le script en local sur votre serveur (linux ou windows) par la commande "php" (debian: package "php-cli" à installer). Cette seconde méthode servira, en outre, à pouvoir automatiser cette fonction de manière autonome (voir post Backup automatique de serveur LAMP).

Bref, à configurer/modifier/optimiser/personnaliser selon vos besoins.

lundi 14 novembre 2011

Exigez le plus pour obtenir le moins

Une technique vieille comme le monde : "exiger le plus pour obtenir le moins"

Ne vous faites plus avoir lors de vos entretiens ou périodes de négociations.
Une solution: Demandez toujours plus !

La plupart du temps, la majorité des gens font preuve d'un certain discernement. Mais, pris par surprise ou au dépourvu, cette stabilité de raisonnement peut se voir souvent dégradée.
Il faut savoir que le système de réflexion de l'homme fonctionne en grande partie par différenciation et comparaison. Toute analyse ne se fait qu'en éclatant l'objet étudié de son environnement.


Le plan est toujours le même:
  1. définissez clairement votre objectif (atteignable avec la personne concernée).
  2. trouvez la cible et proposez un truc énorme, inaccessible. La réaction de la cible ne doit pouvoir être que de l'ordre du refus.
  3. la cible refuse votre première proposition: souffrance émotionnelle ressentie chez la cible car le refus entraine toujours un certain sentiment de culpabilité (c'est pourquoi la cible "s'excuse", la plupart du temps).
  4. montrez alors votre (fausse) déception : intensification de la souffrance de la cible ("remuage" de couteau dans la plaie) qui transforme le sentiment de culpabilité en regrets. La cible se sent redevable et voudrait pouvoir se faire pardonner ;
  5. suggérez alors votre seconde proposition (c'est-à-dire, votre objectif initial) en la minimisant un maximum (utilisez le terme "au moins").
  6. empreinte du sentiment d'être redevable, la cible devrait accepter.
 
Quelques exemples:
- présenter des produits aux tarifs très élevés, pour en vendre d'autres moins chers qui paraissent soudainement beaucoup plus... abordables ; 
- accoster une inconnue dans la rue et lui proposer directement d'aller au cinéma. Refusant alors (sinon prenez la fuite), lui proposer innocemment d'aller "au moins" boire un verre à côté.
- un vendredi, demander à un employé de travailler tout le weekend en sachant pertinemment qu'il ne le pourra pas. Face au refus, lui suggérer qu'il reste "au moins" plus tard ce même soir pour finir.


L'idée est de toujours éviter de demander directement et d'utiliser d'autres chemins pour arriver à votre objectif, ou au contraire, éviter de vous faire manipuler.

Communication interpersonnelle : respectez les distances

Certains d’entre vous l’ont peut-être remarqué : nous nous tenons toujours à une certaine distance lors d'échanges et nous ne nous positionnons jamais de la même manière avec un inconnu, son supérieur, son petit ami, un membre de la famille, etc.

En fait, il existe une sorte de zone invisible mais perceptible, reflétant le degré d'affinité.

La plus éloignée est la distance publique : plus de 7 mètres. Elle s'applique généralement lorsque l’on s’adresse à un grand nombre de personnes. La plupart du temps, il est nécessaire de fortement hausser la voix ou d’utiliser un appareil amplificateur (micro) et la communication est unidirectionnelle (conférencier et son public).

De 1 à 3 mètres, c’est la distance sociale. Elle démontre un certain détachement voulu. Par exemple, un responsable de recrutement qui vous accueille (protégé) derrière son bureau (tel un bunker).

Un peu plus proche, de 0,5 à 1 mètre (en fait, un peu plus que la longueur de votre bras tendu), c'est la distance personnelle. Cette distance indique généralement une bonne entente entre les personnes.
C’est entre cette distance et la distance sociale que vous vous trouvez lorsque vous accostez un inconnu. Attention donc à ne pas trop vous rapprocher sous peine d’empiéter sur le territoire suivant, uniquement réservé aux intimes.

Enfin, vient la distance intime : du contact physique à 0,5 mètre. Les deux interlocuteurs sont souvent très complices. Nous pouvons parler normalement ou aller jusqu’à chuchoter pour être à l’abri d’oreilles extérieures. Les conversations sont plus faciles et spontanées.

Essayez désormais de visualiser ces distances à chaque contact. C’est en quelque sorte  un moyen de vérification pour connaître le jugement que vous porte l’autre. Eloignez-vous d’elle et remarquez si elle se rapproche ou, au contraire, en profite pour s’éloigner aussi. Vous souvenez-vous du malaise que vous aviez eu lorsqu'un lourdeau était venu vous chuchoter quelques mots à vos oreilles ?

Il faut toujours respecter ces différents territoires, être à l'écoute des réactions et évoluer en fonction. 

Balancez une blague ou complimentez la personne, puis observez son impact: si la personne se rapproche d'elle-même et franchit une nouvelle zone, c'est gagné !