Le parseur de wmfs

date
20 / 4 / 2010
comments
0

EDIT: ce billet se voulait une vulgarisation, mais il faut quand même savoir manipuler les notions d'automates, de grammaire etc. Si ça donne envie d'en savoir plus, tant mieux.

En même temps que j'ai laché dwm pour revenir au bon vieux (pas si vieux que ça) wmfs, j'ai totalement recodé le parseur du fichier de configuration.

Un fichier de configuration se doit d'être lisible, et sa projection en mémoire doit être facile à manipuler, malheureusement utiliser le même langage (comme le fait dwm avec son fichier de configuration directement en C) rend la configuration moins convi pour peu que l'on ne soit pas expert. Le parseur sert à ça, traduire un langage simple en langage compréhensible par la machine.

L'idée c'est d'expliquer un peu comment le parseur de wmfs fonctionne parce que ça touche une partie de l'informatique théorique que j'aime bien : l'étude des langages formels.

Yacc et lex sont souvent utilisés pour ce genre de choses. Mon code est juste une implémentation C d'un automate fini déterministe, il ne doit pas être loin du fonctionnement de yacc pour une grammaire et une syntaxe particulière (évidement yacc&lex sont beaucoup plus évolués que mon petit bout de code).

Donc le travail de lex c'est de lire le fichier caractère par caractère et de renvoyer des token, c'est à dire un identifiant syntaxique.

Le langage de configuration est comme ça :

[section]
    option = valeur
    truc = { "liste", 1, False }
    [sous-section]
        [sous-sous-section]
            machin = True
        [/sous-sous-section]
    [/sous-section]
[/section]

Je fais une première transformation purement syntaxique :

enum conf_type { SEC_START, SEC_END, WORD, EQUAL, LIST_START, LIST_END, NONE };
/* qui correspond à  [        [/    un_mot  =        {         }       autre */

Et je range ça dans deux listes chainées, une qui contient des conf_type et une autre qui contient les mots (des char *) :

Liste des TOKEN             Liste des mots (WORD)
SEC_START
WORD                        "section"
WORD                        "option"
EQUAL
WORD                        "valeur"
WORD                        "truc"
EQUAL
LIST_START
WORD                        "liste"
WORD                        "1"
WORD                        "False"
SEC_START
WORD                        "sous-section"
SEC_START
WORD                        "sous-sous-section"
WORD                        "machin"
EQUAL
WORD                        "True"
SEC_END
WORD                        "sous-sous-section"
SEC_END
WORD                        "sous-section"
SEC_END
    WORD                        "section"

Une fois ces deux piles construites, il suffit de dépiler en suivant la grammaire (c'est ce que fait yacc). C'est là qu'on parle d'états finis. Désolé ça aurait été plus compréhensible avec un schéma, mais je sais pas en faire donc j'ai écrit ça avec la syntaxe qu'on utilise pour décrire des grammaires.

start: /* rien */
       | SEC_START WORD sections SEC_END WORD start /* une suite de sections */
       ;

sections: /* rien */
           | SEC_START WORD sections SEC_END WORD /* des sous sections */
           | WORD EQUAL option sections /* des options */
           ;
option: WORD /* un mot tout simple (une valeur quoi) */
          | LIST_START list LIST_END /* une liste de valeurs */
          ;
 list: /* rien */
     | WORD list /* une liste de mots */
     ;

Le parseur va ainsi remplir une structure générique capable de contenir toutes les configurations qui respectent la grammaire et la syntaxe.

Pour wmfs en gros c'est ça :

struct conf_opt { /* options */
    char *name;
    char *val[10]; /* au plus 10 éléments dans une liste */
    SLIST_ENTRY(conf_opt) entry;
}
struct conf_sec { /* section */
    char *name;
    SLIST_HEAD(, conf_opt) optlist; /* liste chaînée des options de la section */
    TAILQ_HEAD(, conf_sec) sub; /* liste chaînée des sous sections */
    TAILQ_ENTRY(conf_sec) entry;
}

J'utilise abusivement des macros de <sys/queue.h>.

Ensuite il suffit de coder une API qui va récupérer des options/sections précises sur la structure, mais c'est pas le plus dur à faire.

Voilà un peu l'idée, l'intérêt de cette méthode c'est qu'elle est certaine (pas ou peu de bugs incompréhensibles), rapide : le fichier de configuration n'est traversé qu'une seule fois, pas besoin de strlen(), strchr(), strcmp(), strstr() qui prennent des plombes.

EDIT : j'ai failli oublier de donner le lien vers le code

les coredump sous Linux

date
15 / 4 / 2010
comments
0

Sous FreeBSD les coredump sont automatiquement générés, voyez plutôt l'entrée default de /etc/login.conf(5) avec le coredumpsize=unlimited.

Pour faire la même chose sous Linux (en l'occurrence sur Archlinux ce n'est pas le cas par defaut) :

Dans /etc/sysctl.conf :

kernel.core_pattern = %e.core

Et pour rendre la taille des .core illimitée j'ai édité mon ~/.zprofile :

ulimit -c unlimited

Et pour rendre les changement effectifs :

sysctl kernel/core_pattern=%e.core
# Si vous n'avez pas sysctl :
echo "%e.core" | sudo tee /proc/sys/kernel/core_pattern
# et on se reconnecte

Et pour tester :

cat > foo.c << EOF
#include <stdlib.h>
int main(void) { char *p = malloc(sizeof(*p)); free(p+1); return 0; }
EOF
cc foo.c && ./a.out
[.....]
zsh: abort (core dumped)  ./a.out
# \o/
gdb ./a.out a.out.core
....

realpath secure ?

date
15 / 3 / 2010
date
C
comments
3

Dans le cadre d'une application serveur (type transfert de fichier : http, ftp, ...), pour contrôler l'accès aux fichiers j'utilise realpath(3). Cette fonction permet d'avoir le path (chemin) réel d'un fichier, sans lien relatif dans un des dossiers. Ainsi on a juste à faire un strncmp(3) pour le comparer à la racine de l'application.

Par exemple une partie du code du serveur HTTP que je suis en train de coder (uri est quelque chose de la forme /../foo/bar/../machin.html et conf_root la racine du style /usr/local/www/

char path[PATH_MAX];
char root[PATH_MAX];

if (!realpath(uri+1, path) ||
        !realpath(conf_root, root) ||
        strncmp(path, root, strlen(root)) != 0)
    return send_error(404);

Prenez ce post pour une question, à part le chroot(2) et realpath, comment bien contrôler l'accès aux fichiers ?

J'y connais rien en filesystems, mais si on pouvait avoir un moyen rapide de savoir si un fichier est dans tel ou tel fichier ce serait grandement pratique. Sans me vanter j'ai souvent des idées géniales en informatique, mais bien souvent ça a été pensé et implémenté depuis plus de 10 ans ! Du coup si vous avez plus d'information, je suis prêt à converser sur ce sujet avec vous...

IPV6 step one two

date
4 / 3 / 2010
comments
0

La semaine dernière, j'expliquais comment j'avais intégré l'IPV6 dans mon réseau. Depuis la donne à fortement changé puisque j'ai mis ma freebox en mode non routeur, et donc mon propre routeur sous FreeBSD (ma nouvelle carte wifi marche pas sous OpenBSD).

Du coup on en arrive au problème de la freebox qui se comporte en routeur IPV6 même en mode non routeur, voyez plutôt cette discussion FRNOG ou on nous explique que ça va peut être changer bientôt. En attendant, pas moyen de router proprement l'IPV6 de Free sans passer par un proxy ndp ou un bridge logiciel, sans compter qu'il n'est pas possible d'avoir un reverse DNS avec l'IPV6 de Free.

Grâce à sieur bsdsx j'ai découvert tunnelbrocker.net qui est un service type tunnel broker en gros les paquet IPV6 sont encapsulés dans de l'IPV4 entre chez vous et le bout du tunnel (plusieurs sont disponibles suivant votre localisation), ils fournissent un /64 ou un /48 et surtout la possibilité d'avoir un reverse DNS.

Sachez qu'il y a encore une troisième façon d'avoir une connectivité IPV6, basé sur la RFC 3068 ou encore 6to4, qui permet à n'importe qui d'obtenir un /48 sans demander rien à personne (c'est la passerelle 6to4 la plus proche de vous qui prend en charge le routage de votre traffic), le prefixe est calculé à partir de votre IPV4 publique.

J'ai grosso modo donné les 3 manière communes d'accéder au réseau IPV6 quand on est abonné chez Free. Chaque méthode à son avantage (ou pas), vous pouvez lire ce mail.

Perso j'ai choisi le tunnel broker Huricane Electrics qui marche bien, il suffit de créer un compte et un tunnel, ils donnent même la config pour FreeBSD mais pour fixer ça en dur dans rc.conf :

ipv6_enable="YES"
ipv6_gateway_enable="YES"
gif_interfaces="gif0"
gifconfig_gif0="82.229.137.130 216.66.84.46" # Mon IP publique suivie de l'IP du tunnel
ipv6_ifconfig_gif0="2001:470:1f14:7bf::2 2001:470:1f14:7bf::1 prefixlen 128"
ipv6_defaultrouter="2001:470:1f14:7bf::1"
# On relie le réseau local (sur l'interface vr1 chez moi)
ipv6_ifconfig_vr1="2001:470:1f15:7bf:dead:c0de::1 prefixlen 96"

La conf pf qui va avec :

ext_if = "vr0"
int_if  = "vr1"
gif_if  = "gif0"
tun_end = "216.66.84.46" # Le bout du tunnel

localnet = $int_if:network

block log all
pass inet proto icmp all icmp-type { echoreq unreach }
pass inet6 proto icmp6 all icmp6-type { echoreq unreach timex toobig neighbrsol neighbradv }
pass out proto { tcp udp } all
pass out on $ext_if from $ext_if to $tun_end
# On peut filtre ici (là tout passe sans filtrage)
pass in on $gif_if inet6 proto { tcp udp icmp6 } to $localnet

En cas de doutes, vous pouvez faire des nmap depuis l'interface web de tunnelbroker.net.

Pour la config du reverse DNS c'est pas plus compliqué qu'en IPV4 (juste plus long) :

; master/local6.rev
$TTL 3600 ; 1 heure
f.b.7.0.5.1.f.1.0.7.4.0.1.0.0.2.ip6.arpa.   IN  SOA ns.philpep.org. root.philpep.org. (
            2010022601  ; serial
            8H          ; refresh
            4H          ; retry
            4W          ; expire
            1D )        ; Min TTL

@       IN       NS ns.philpep.org.
1.0.0.0.0.0.0.0.e.d.0.c.d.a.e.d     IN      PTR solo.philpep.org.
; et named.conf
zone "f.b.7.0.5.1.f.1.0.7.4.0.1.0.0.2.ip6.arpa" {
    type master;
    file "master/local6.rev";
};

Autre chose à savoir, l'IPV6 c'est pas encore ça puisqu'on est encore obligé à un moment ou un autre d'encapsuler l'IPV6 dans l'IPV4 et de passer par des tunnels plus ou moins loin, la connectivité sortante en souffre énormément (allez faire un tour sur youtube.com en IPV6 en passant par un tunnel broker...), du coup ça peut être une bonne idée de préférer l'IPV4 sur la machine où il y a le browser :

/etc/rc.d/ip6addrctl prefer_ipv4
# ou alors QUE pour firefox about:config
network.dns.disableIPv6

Si quelqu'un à déjà mis en place son réseau IPV6 en 6to4 avec stf(4), j'ai lu que la connectivité entrante était moins fiable qu'avec un tunnel broker, je voudrais que quelqu'un m'explique ??

Quelques liens :

Astuce pf du jour

date
23 / 2 / 2010
comments
0

J'ai la chance d'avoir une bibliothèque avec un rayon informatique bien rempli dans mon Université, dernièrement j'y ai emprunté ce bouquin. Là dedans j'y ai vu une syntaxe pour décrire le réseau local que j'avais raté quand j'ai lu la doc :

ext_if = "rl0"
# Au lieu de
localnet = "{ 192.168.0.0/24, 2a01:e35:2e58:9820::/64 }"
# On peut mettre
localnet = $ext_if:network
# Ou encore
localnet = rl0:network

Pf va lui même calculer le réseau local à l'aide de l'IP du netmask et du prefixe.

Attention tout de même si vous avez plusieurs IP (v4 et v6) sur l'interface, ça peut produire des règles redondantes, en cas de doute :

pfctl -sr

EDIT (27/02/10) : En fait quand il y a des alias sur l'IP il va les évaluer mais pour ne spécifier que l'adresses (sans les alias) on peut mettre $ext_if:network:0