Programmation

Gandi Live DNS v5

Thursday, 26 April 2018
|
Écrit par
Grégory Soutadé

La toute récente sortie de Pannous a été l'occasion de créer un nouveau sous-domaine pour héberger le service. Service qui comporte une partie d'authentification, donc obligation de passer par une communication sécurisée (SSL/TLS). Autrefois, la chose était plus aisée, puisque je pouvais générer mes propres certificats et notamment des certificats "wildcards", donc valides pour tous les sous-domaines.

Sauf que je suis passé à Let's Encrypt. Il a donc fallu attendre la sortie de la version 2 du protocole (qui a eu du retard) afin de bénéficier de cette fonctionnalité. Surtout, qu'au passage, le paquet Debian (backport) de certbot a été cassé, ce qui m'a forcé à revenir à une version encore plus ancienne.

Bref, les choses sont maintenants stables et déployées sur les serveurs respectifs. Petit problème néanmoins, la génération d'un certificat wildcard par Let's Encrypt requiert l'ajout d'une entrée DNS (comme challenge). Fatalité, le DNS de Gandi a lui aussi évolué pour passer en version 5. Avec pour principal avantage une mise à jour immédiate des entrées DNS (là où il fallait plusieurs minutes/heures auparavant). Autre nouveauté : l'API Gandi change de format. On oublie l'ancien XML-RPC (ce qui était pratique avec des bindings Python déjà tout faits), pour passer au REST (un peu moins formel).

Mélangeons tout ça pour obtenir un joli cocktail, dont la recette nous est donnée par Sébastien Blaisot qui, pour nous simplifier la vie, a créé des scripts de génération de certificats wildcards via Let's Encrypt. Le code est disponible sur GitHub et supporte le logiciel bind (en local), l'API OVH et la nouvelle API Gandi. Il ne reste plus qu'à cloner le dépôt et lancer la commande magique :

cd certbot-dns-01-authenticators/gandi-livedns
certbot certonly --manual --server https://acme-v02.api.letsencrypt.org/directory\
 --manual-auth-hook $PWD/auth.py --manual-cleanup-hook $PWD/cleanup.py -d '*.soutade.fr'

Et voilà un joli certificat tout frais !

Du coup, je me suis grandement inspiré de son code pour mettre à jour mon script de DNS fallback (serveur de secours via redirection DNS). Avec, en prime, un passage en Python 3 ! À terme, il faudra que j'ajoute le support IPv6.

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import requests
import json
import re

# Config
domain="soutade.fr"
API_KEY = "YOUR-KEY"
livedns_api = "https://dns.api.gandi.net/api/v5/"
dyndns_url = 'http://checkip.dyndns.com/'
headers = {
    'X-Api-Key': API_KEY,
}
A_RECORD_NAME="@" # Record of A type

# Get current IP
current_ip = ''
response = requests.get(dyndns_url)
if response.ok:
    pattern = re.compile('[^:]*(\d+\.\d+\.\d+\.\d+)')
    result = pattern.search(response.text, 0)
    if result == None:
        print("No IP found")
        exit(1) 
    else:
        current_ip = result.group(0).strip()
else:
    print("Connexion error")
    response.raise_for_status()
    exit(1)

print('Your Current IP is %s' % (current_ip))

# Retrieve domains address
response = requests.get(livedns_api + "domains", headers=headers)

if (response.ok):
    domains = response.json()
else:
    response.raise_for_status()
    exit(1)

domain_index = next((index for (index, d) in enumerate(domains) if d["fqdn"] == domain), None)

if domain_index == None:
    # domain not found
    print("The requested domain " + domain + " was not found in this gandi account")
    exit(1)

domain_records_href = domains[domain_index]["domain_records_href"]

# Get recorded IP
response = requests.get(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers)

if (response.ok):
    record = response.json()
else:
    print("Failed to look for recorded IP")
    response.raise_for_status()
    exit(1)

print('Old IP : %s' % (record['rrset_values'][0]))

if current_ip != record['rrset_values'][0]:
    record['rrset_values'][0] = current_ip

    # Put updated IP
    response = requests.put(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers, json=record)

    if (response.ok):
        print("IP updated")
    else:
        print("something went wrong")
        response.raise_for_status()
        exit(1)

    exit(34) # IP updated return !!

exit(0)

Une version téléchargable est disponible ici.

Pannous v0.1

Thursday, 26 April 2018
|
Écrit par
Grégory Soutadé

Logo Pannous

Il y a quelques temps déjà, Mozilla a décidé d'abandonner son format et ses API pour les extensions de son navigateur (Firefox) afin de tout migrer vers les WebExtensions. Ces dernières reprennent en très grande partie les caractéristiques de son concurrent Chrome. Cet événement, à priori négatif, m'a permis d'améliorer la sécurité de mon extension gPass et de factoriser grandement le code. Malheureusement, la migration de la version 0.7 (ancien format) à la version 0.8 n'est pas automatique (contrairement à Chrome) et beaucoup d'utilisateurs se sont perdus en chemin. C'est à ce moment-là que je me suis rendu compte qu'il me manquait un lien avec les utilisateurs de manière générale.

Il est vrai que ma forge inDefero n'est plus maintenue depuis maintenant 8 ans, qu'il n'y a pas de fonctionnalité "sociale" ou de "distribution" pour prévenir d'éventuelles nouveautés. Pour autant, je n'ai pas envie de passer sur une autre plateforme (notamment Github) et je ne trouve aucun remplaçant aussi ergonomique. La plupart des candidats ne se contentant que d'imiter la plateforme propriétaire majeure. L'idée, toute simple, est donc de créer des listes de diffusion projets (mailing list ou announce list).

La référence dans le domaine est GNU mailman. Je pourrais y faire les mêmes reproches que pour les forges : compliqué à configurer, moche, lourd. Alors que mes besoins sont assez légers. Du coup, je me suis lancé dans l'écriture d'un petit moteur de liste de diffusion. À priori, rien de compliqué : un petit script pour la gestion des mails et une gestion assez simples des listes/groupes/utilisateurs (ajout, modification, suppressions, droits). Le tout en PHP : simple, rapide et avec tous les modules nécessaires. En effet, j'ai décidé d'abandonner mon Django favoris, car, s'il aurait pu répondre à tous mes besoins, il est extrêmement lent pour mon petit serveur.

Au début, j'ai récupéré un peu de code d'un ancien projet avec un framework perso (simpliste). Mais en complexifiant un tout petit peu le projet, je me suis dit qu'il était inutile de ré inventer la roue. J'ai donc fait le tour des frameworks PHP (des plus lourds et connus aux plus légers). C'est assez impressionnant de voir que certains font des centaines de méga pour une application de moins de 2MO... Après comparaison, il s'avère qu'ils adoptent tous la même philosophie HMVC et l'on peut passer de l'un à l'autre assez rapidement. Après avoir longtemps lorgné sur CakePHP, j'ai finalement décidé de me lancer en FuelPHP. J'y retrouve toutes les fonctionnalités que j'attends, en plus d'être léger et rapide.

Bref, tout ça pour annoncer la création de Pannous pour PHP Annoucement System. L'objectif est d'être simple à installer, configurer, utiliser. On y retrouve deux fonctionnalités inédites :

  • Plusieurs niveaux de droits pour l'émission d'un mail sur la liste (écrivains, lecteurs, utilisateurs, tout le monde), avec la possibilité de vérifier la signature GPG.
  • API Rest pour l'inscription/désinscription d'une liste (avec génération automatique de code HTML/AJAX).

Pour le reste, c'est du classique : gestion des utilisateurs, des groupes, des listes, inscription/désinscription en ligne ou par mail...

Le projet est disponible sur ma Forge sous licence GPL v3.

Naturellement, j'ai créé une liste pour Pannous : pannous-announce@soutade.fr

gPass 0.8.1

Friday, 15 December 2017
|
Écrit par
Grégory Soutadé

Logo gPass

Mise à jour mineure de gPass suite à la "violation" des règles d'utilisation de Google : il manquait les informations concernant la gestion des données utilisateurs (vie privée/privacy policy). J'en profite pour pousser deux autres changements :

  • Léger changement dans l'algorithme de génération des wildcards côté client (*.nomde.domain), qui ne fonctionnait pas pour des noms de domaines courts (<= 3 caractères)
  • Côté serveur, l'affichage descend automatiquement lors de l'ajout d'une nouvelle entrée (pratique quand on a beaucoup de mots de passes)

Tips : Linux kernel coding rules

Friday, 01 December 2017
|
Écrit par
Grégory Soutadé

Tux, the Linux mascot

For some projects I have to work inside Linux kernel code and, as a developer, I have my own preferences for coding rules (not stable for variable/function naming in facts...) . Especially, I prefer 4 spaces for indentation, curly brackets at a newline. But, the kernel is full of narcissists dictatorscoders and a strict set of coding rules has been determined some years ago. It can be found in Documentation/CodingStyle. Linux is a big project with thousands of people working on it, so I agree that it requires some code normalization for all contribution. Even if you don't plan to verse your patches into upstream, it's good to follow these rules. Here is some tips to comply with it.

First, as an emacs user I have my own rules in my ~/.emacs configuration file. But, when I work on Linux kernel, I want "linux" rules to be applied. A tip from emacswiki allows to automatically switch when a file with "linux" in its path name is found :

(defun maybe-linux-style ()
  (when (and buffer-file-name
         (string-match "linux" buffer-file-name))
    (c-set-style "Linux")
    (c-set-indentation-style "linux")
    (indent-tabs-mode t)
    ))
(add-hook 'c-mode-hook 'maybe-linux-style)
(add-hook 'before-save-hook 'delete-trailing-whitespace)

Another tip I use is a modified pre-commit hook that will checks my modifications before validate the commit. Edit you .git/hooks/pre-commit with the following lines :

#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

temp_file=`mktemp`
files=`git diff --name-only`
to_diff=""
for file in ${files} ; do
    # Filter .c and .h files
    echo ${file} | grep ".h$" > /dev/null 2>&1
    if [ $? -ne 0 ] ; then
    echo ${file} | grep ".c$" > /dev/null 2>&1
    if [ $? -ne 0 ] ; then
        continue
    fi
    fi
    # Add not deleted file
    if [ -f ${file} ] ; then
    to_diff="${to_diff} ${file}"
    fi
done
git diff --no-color -u --summary ${to_diff} > ${temp_file}
./scripts/checkpatch.pl --no-signoff --min-conf-desc-length=0 --no-summary --mailback ${temp_file}
ret=$?
rm -f ${temp_file}
exit $ret

Don't forget chmod +x .git/hooks/pre-commit

Unfortunately, this hook can't be stored into the central repository and have to be copied each time you clone it (or install a server hook). If you want to bypass it (for some reasons), just commit with "--no-verify" option.

Finally, when you already have a custom codebase ready and commited, you can use some scripts provided by the kernel team to check for coding rules. The first is scripts/Lindent that will indent your file following the kernel coding rules via indent util (needs to be installed). The second is scripts/cleanfile which remove unnecessary whitespaces. The third is scripts/checkpatch.pl -f that will checks the whole file (and not just a patch).

Division by 10

Wednesday, 27 September 2017
|
Écrit par
Grégory Soutadé

For a human, it's pretty simple to divide a number by ten because we used to calculate in ten base everyday. But ... a computer handle numbers in base 2. It doesn't means that it can't compute a division with a number that is not a power of 2, but operations are really faster in this base. Especially if you don't have floating point unit.

At work, I had to re implement the function "printf". To display decimal integers, I use an algorithm like :

while (value)
{
    *cur_ptr = '0' + (value%10);
    value = value/10;
    ...
}

This works fine, but two GCC builtin functions udivdi3 and umoddi3 are called which represent an amount of 3.5kB of code. So, I was looking for a code size optimized implementation on the Internet and didn't found my way.

Finally, I wrote my own. It's a basic one inspired from child learning method :

01. void div10(unsigned value, unsigned* _res, unsigned* _mod)
02. {
03.    unsigned res = value / 8;
04.    unsigned mod = value - (res*10);
05.
06.    while (mod > 10)
07.    {
08.        res -= 1;
09.        mod += 10;
10.    }
11.
12.    *_res = res;
13.    *_mod = mod;
14. }

This algorithm is a basic approach to division. It tries all numbers until it find the good one.

First thing : why I use variables instead of directly write values to pointers ? It's to indicate to GCC that they are temporary values which can be kept into registers and not written every loop into the memory (save instructions and memory accesses).

Line 3 is the begining. We will start at value / 8 which can be easily done by the computer because it is equivalent to a right shift of 3 bits (only one instruction). Note that 8 is the closest power of two to 10 and x/8 is greater than x/10 .

Line 4 is the computation of difference (distance) between my result multiplied by 10 and the current value. For the final result, this difference must be less than 10 (which correspond to the modulo).

Line 6 : while its not the case, we decrement result and increment modulo. Why incrementing modulo ? It's an optimization of the re computation of :

mod = value - (res*10);

If res is decremented, modulo is incremented as value is fixed. So, a simple addition is sufficient here.

There is another big trick in this code : the substract line 4 is done with UNSIGNED values and the result of line 4 is most of the time negative ! Which corresponds to big unsigned value (> 2147483648) that also implies > 10. We have to wait an integer overflow for mod to become positive and when it's done, we get the current modulo value (at least MAXLONGINT+10 = 9) !

If we does opposite operation ((res*10) - value), we have to decrement mod until it becomes less or equals to 0. But, in this case, all operations must be done in signed mode and the final modulo must be inverted at the end (more instructions) :

void div10(unsigned long value, unsigned long* _res, unsigned long* _mod)
{
    unsigned long res = value / 8;
    unsigned long mod = (res*10) - value;

    while (((signed long)mod) > 0)
    {
        res -= 1;
        mod -= 10;
    }

    *_res = res;
    *_mod = (unsigned long)-(signed long)mod;
}

Facts : the unsigned version of my algorithm is 15 instructions while udivdi3 + umoddi3 is 881 instructions. Wonderful !

Beware : this algorithm is slow. For small numbers it's not import because x/8 =~ x/10, but when x becomes bigger, the difference can be huge and requires one decrement, one increment and one test multiplied (x/8 - x/10)/10 times. For 32 bits numbers, it's 10 737 418 loops...

This algorithm can be extended to any divisor by replacing hardcoded divisor with a parameter and a function that finds the nearest and inferior power of 2.

void div(unsigned long value, unsigned long divisor, unsigned long* _res, unsigned long* _mod)
{
    unsigned long res, mod, tmp = divisor, power2 = 0;

    /* Nearest inferior power of 2 */
    while (tmp > 1)
    {
        tmp >>=1;
        power2++;
    }

    res = value >> power2;
    mod = value - (res*divisor);

    while (mod > divisor)
    {
        res -= 1;
        mod += divisor;
    }

    *_res = res;
    *_mod = mod;
}