#!/bin/sh


#
# Cration de la base DNS
#
# Syntaxe :
#	creer-base-dns
# (pas d'argument)
#
# Historique
#   2002/02/11 : pda        : conception
#   2002/02/12 : pda/jean   : modifications pour coller au mcd original
#   2002/04/19 : pda/jean   : contrainte unique sur nom+domaine de rr
#   2002/04/19 : pda        : contrainte unique sur le nom de domaine
#   2002/04/23 : pda        : ajout de la priorit dans les droits de domaine
#   2002/04/23 : pda/jean   : ajout de la gestionde la gnration des zones
#   2002/05/02 : pda/jean   : table zone objectise
#   2002/05/06 : pda        : id pour la table zone
#   2002/05/21 : pda        : ajout de la table communaute
#   2004/01/22 : pda        : adaptation au nouveau format de base
#   2005/04/08 : pda        : adaptation au nouveau format de base (v 1.3)
#   2007/11/27 : pda/jean   : validation avec pgsql 8.2
#   2007/11/27 : pda/jean   : ajout de la table log
#

BASE=dns

PGPASSWORD="mot-de-passe-de-dns"
export PGPASSWORD

NOBODY="dns"			# utilisateur(s) non privilgi(s)
ROOT="pda jean"			# utilisateur(s) privilgi(s)

##############################################################################
# POUR EVITER LES ERREURS (LANCEMENT MALENCONTREUX DE CE SCRIPT)

exit 0

##############################################################################

dropdb $BASE > /dev/null 2> /dev/null

createdb -E unicode $BASE

createlang pltcl $BASE

psql --quiet $BASE <<'EOF'

    -- les domaines

    CREATE SEQUENCE seq_domaine START 1 ;
    CREATE TABLE domaine (
	iddom		INT		-- identifiant du domaine
		DEFAULT NEXTVAL ('seq_domaine'),
	nom		TEXT,		-- le domaine (ex: "u-strasbg.fr")

	UNIQUE (nom),
	PRIMARY KEY (iddom)
    ) ;

    -- les correspondants et les groupes

    CREATE SEQUENCE seq_groupe START 1 ;
    CREATE TABLE groupe (
	idgrp		INT		-- identifiant du correspondant
		DEFAULT NEXTVAL ('seq_groupe'),
	nom		TEXT,		-- login sur le serveur
	admin		INT DEFAULT 0,	-- 1 si root, 0 si utilisateur normal
	droitsmtp	INT DEFAULT 0,	-- 1 si droit de grer les metteur SMTP
	droitttl	INT DEFAULT 0,	-- 1 si droit d'diter TTL d'une machine

	UNIQUE (nom),
	PRIMARY KEY (idgrp)
    ) ;

    CREATE SEQUENCE seq_corresp START 1 ;
    CREATE TABLE corresp (
	idcor		INT		-- identifiant du correspondant
		DEFAULT NEXTVAL ('seq_corresp'),
	login		TEXT,		-- login sur le serveur
	present		INT,		-- 1 si present, 0 si plus l
	idgrp		INT,		-- le groupe qui refrence ses droits

	UNIQUE (login),
	FOREIGN KEY (idgrp) REFERENCES groupe (idgrp),
	PRIMARY KEY (idcor)
    ) ;

    -- la description des rseaux, des communauts et des tablissements

    CREATE SEQUENCE seq_etablissement START 1 ;
    CREATE TABLE etablissement (
	idetabl		INT		-- identifiant de l'tablissement
		DEFAULT NEXTVAL ('seq_etablissement'),
	nom		TEXT,		-- "Universit Louis Pasteur"

	PRIMARY KEY (idetabl)
    ) ;

    CREATE SEQUENCE seq_communaute START 1 ;
    CREATE TABLE communaute (
	idcommu		INT		-- identifiant de la communaute
		DEFAULT NEXTVAL ('seq_communaute'),
	nom		TEXT,		-- "Administration"

	PRIMARY KEY (idcommu)
    ) ;

    CREATE SEQUENCE seq_reseau START 1 ;
    CREATE TABLE reseau (
	idreseau	INT		-- identification du rseau
		DEFAULT NEXTVAL ('seq_reseau'),
	nom		TEXT,		-- nom (ex: "serveurs CRC")
	localisation	TEXT,		-- localisation prcise ventuelle
	adr4		CIDR,		-- plage IPv4 du rseau
	adr6		CIDR,		-- plage IPv6 du rseau
	idetabl		INT,		-- l'tablissement d'appartenance
	idcommu		INT,		-- ADM, ENS, RECH
	commentaire	TEXT,		-- texte libre
	dhcp		INT DEFAULT 0,	-- activer DHCP (1) ou non (0)
	gw4		INET,		-- routeur IPv4 par dfaut du rseau
	gw6		INET,		-- routeur IPv6 par dfaut du rseau


	CONSTRAINT au_moins_un_prefixe_v4_ou_v6
	    CHECK (adr4 IS NOT NULL OR adr6 IS NOT NULL),
	CONSTRAINT gw4_in_net CHECK (gw4 <<= adr4),
	CONSTRAINT gw6_in_net CHECK (gw6 <<= adr6),
	CONSTRAINT dhcp_needs_ipv4_gateway
	    CHECK (dhcp = 0 OR (dhcp != 0 AND gw4 IS NOT NULL)),
	FOREIGN KEY (idetabl) REFERENCES etablissement (idetabl),
	FOREIGN KEY (idcommu) REFERENCES communaute    (idcommu),
	PRIMARY KEY (idreseau)
    ) ;


    -- les types de machines

    CREATE SEQUENCE seq_hinfo MINVALUE 0 START 0 ;
    CREATE TABLE hinfo (
	idhinfo		INT		-- le type de machine
		DEFAULT NEXTVAL ('seq_hinfo'),
	texte		TEXT,		-- le type en clair
	tri		INT,		-- classe de tri
	present		INT,		-- prsent dans l'application
	PRIMARY KEY (idhinfo)
    ) ;

    -- les plages de rseau gres par les correspondants

    CREATE TABLE dr_reseau (
	idgrp		INT,		-- le groupe qui gre le rseau
	idreseau	INT,		-- le rseau
	tri		INT,		-- classe de tri pour l'affichage
	dhcp		INT DEFAULT 0,	-- accs  la gestion DHCP (dynamique)
	acl		INT DEFAULT 0,	-- accs aux ACL (plus tard...)

	FOREIGN KEY (idgrp)    REFERENCES groupe (idgrp),
	FOREIGN KEY (idreseau) REFERENCES reseau (idreseau),
	PRIMARY KEY (idgrp, idreseau)
    ) ;

    -- les droits associs aux groupes

    CREATE TABLE dr_dom (
	idgrp		INT,		-- le groupe
	iddom		INT,		-- texte du domaine
	tri		INT,		-- pour l'affichage dans les menus
	rolemail	INT DEFAULT 0,	-- groupe a accs aux rles mail
	roleweb		INT DEFAULT 0,	-- groupe a accs aux rles web

	FOREIGN KEY (idgrp) REFERENCES groupe (idgrp),
	PRIMARY KEY (idgrp, iddom)
    ) ;

    CREATE TABLE dr_ip (
	idgrp		INT,		-- le groupe
	adr		CIDR,		-- portion de rseau
	allow_deny	INT,		-- 1 = allow, 0 = deny

	FOREIGN KEY (idgrp) REFERENCES groupe (idgrp),
	PRIMARY KEY (idgrp, adr)
    ) ;

    -- les profils DHCP

    CREATE SEQUENCE seq_dhcpprofil START 1 ;
    CREATE TABLE dhcpprofil (
	iddhcpprofil	INT		-- identifiant du profil DHCP
		DEFAULT NEXTVAL ('seq_dhcpprofil'),
	nom 		TEXT UNIQUE,	-- nom du profil
	texte		TEXT,		-- texte  ajouter avant les hosts

	CHECK (iddhcpprofil >= 1),
	PRIMARY KEY (iddhcpprofil)
    ) ;

    -- les droits associs aux profils DHCP

    CREATE TABLE dr_dhcpprofil (
	idgrp		INT,		-- identifiant du groupe
	iddhcpprofil	INT,		-- identifiant du profil DHCP
	tri		INT,		-- classe de tri pour les menus

	FOREIGN KEY (idgrp)        REFERENCES groupe     (idgrp),
	FOREIGN KEY (iddhcpprofil) REFERENCES dhcpprofil (iddhcpprofil),
	PRIMARY KEY (idgrp, iddhcpprofil)
    ) ;

    -- les intervalles d'adresses dynamiques DHCP

    CREATE SEQUENCE seq_dhcprange START 1 ;
    CREATE TABLE dhcprange (
	iddhcprange	INT		-- seulement pour l'dition de tableau
		DEFAULT NEXTVAL ('seq_dhcprange'),
	min 		INET UNIQUE,	-- dbut de l'intervalle dynamique
	max		INET UNIQUE,	-- fin de l'intervalle dynamique
	iddom		INT,		-- domaine fourni par DHCP
	default_lease_time INT DEFAULT 0, -- en secondes
	max_lease_time	INT DEFAULT 0,	-- en secondes
	iddhcpprofil	INT,		-- profil autoris pour dhcprange

	CHECK (min <= max),
	FOREIGN KEY (iddom) REFERENCES domaine (iddom),
	FOREIGN KEY (iddhcpprofil) REFERENCES dhcpprofil(iddhcpprofil),
	PRIMARY KEY (iddhcprange)
    ) ;


    -- les RR

    CREATE SEQUENCE seq_rr START 1 ;
    CREATE TABLE rr (
	idrr		INT		-- l'objet
		DEFAULT NEXTVAL ('seq_rr'),
	nom		TEXT,		-- le nom de l'objet (premier composant)
	iddom		INT,		-- domaine de l'objet

	mac		MACADDR UNIQUE,	-- adresse MAC associe au nom, ou NULL
	iddhcpprofil	INT,		-- identifiant du profil DHCP ou NULL

	idhinfo		INT DEFAULT 0,	-- index dans la table hinfo
	commentaire	TEXT,		-- un champ "commentaire" en fait
	respnom		TEXT,		-- nom du responsable
	respmel		TEXT,		-- adresse du responsable

	idcor		INT,		-- auteur de la dernire modification
	date		TIMESTAMP (0) WITHOUT TIME ZONE	-- date dernire modif.
			    DEFAULT CURRENT_TIMESTAMP,
	droitsmtp	INT DEFAULT 0,	-- metteur SMTP autoris
	ttl		INT DEFAULT -1,	-- TTL si diffrent de la zone

	FOREIGN KEY (idcor)        REFERENCES corresp    (idcor),
	FOREIGN KEY (iddom)        REFERENCES domaine    (iddom),
	FOREIGN KEY (iddhcpprofil) REFERENCES dhcpprofil (iddhcpprofil),
	FOREIGN KEY (idhinfo)      REFERENCES hinfo      (idhinfo),
	UNIQUE (nom, iddom),
	PRIMARY KEY (idrr)
    ) ;

    CREATE TABLE rr_ip (
	idrr		INT,		-- l'objet
	adr		INET,		-- adresse IP (v4 ou v6)

	FOREIGN KEY (idrr) REFERENCES rr (idrr),
	PRIMARY KEY (idrr, adr)
    ) ;

    CREATE TABLE rr_cname (
	idrr		INT,		-- l'objet
	cname		INT,		-- l'objet point

	FOREIGN KEY (idrr)  REFERENCES rr (idrr),
	FOREIGN KEY (cname) REFERENCES rr (idrr),
	PRIMARY KEY (idrr, cname)
    ) ;

    CREATE TABLE rr_mx (
	idrr		INT,		-- l'objet
	priorite	INT,		-- priorite
	mx		INT,		-- objet point par le mx

	FOREIGN KEY (idrr) REFERENCES rr (idrr),
	FOREIGN KEY (mx)   REFERENCES rr (idrr),
	PRIMARY KEY (idrr, mx)
    ) ;

    -- les rles "web"
    CREATE TABLE role_web (
	idrr		INT,		-- id du "serveur web"

	FOREIGN KEY (idrr) REFERENCES rr (idrr),
	PRIMARY KEY (idrr)
    ) ;

    -- les rles "mail"
    CREATE TABLE role_mail (
	idrr		INT,		-- id de "l'adresse de messagerie"
	heberg		INT,		-- id du rr de l'hbergeur des mboxes

	FOREIGN KEY (idrr)   REFERENCES rr (idrr),
	FOREIGN KEY (heberg) REFERENCES rr (idrr),
	PRIMARY KEY (idrr)
    ) ;

    -- table des groupes ayant droit de grer des boites aux lettres
    -- de "l'adresse de messagerie"
    CREATE TABLE dr_mbox (
	idgrp		INT,		-- id du groupe
	idmail		INT,		-- id de "l'adresse de messagerie"

	FOREIGN KEY (idgrp)  REFERENCES groupe (idgrp),
	FOREIGN KEY (idmail) REFERENCES role_mail (idrr),
	PRIMARY KEY (idgrp, idmail)
    ) ;

    -- table des relais de messagerie pour les adresses d'un domaine
    CREATE TABLE relais_dom (
	iddom		INT,		-- id du domaine
	priorite	INT,		-- priorit du MX  gnrer
	mx		INT,		-- id du rr du relais pour ce domaine

	FOREIGN KEY (iddom) REFERENCES domaine (iddom),
	FOREIGN KEY (mx)    REFERENCES rr      (idrr),
	PRIMARY KEY (iddom, mx)
    ) ;


    -- la gnration des zones DNS

    CREATE SEQUENCE seq_zone START 1 ;
    CREATE TABLE zone (
	idzone		INT		-- l'id de la zone
		DEFAULT NEXTVAL ('seq_zone'),
	domaine		TEXT,		-- le domaine  gnrer
	version		INT,		-- numro de version dans la zone
	prologue	TEXT,		-- prologue de la zone (trou %VERSION%)
	rrsup		TEXT,		-- ajout  chaque gnration de nom
	generer		INT,		-- modifie depuis dernire gnration

	UNIQUE (domaine),
	PRIMARY KEY (idzone)
    ) ;

    CREATE TABLE zone_normale (
	selection	TEXT		-- critre de slection des noms
    ) INHERITS (zone) ;

    CREATE TABLE zone_reverse4 (
	selection	CIDR		-- critre de slection des adresses
    ) INHERITS (zone) ;

    CREATE TABLE zone_reverse6 (
	selection	CIDR		-- critre de slection des adresses
    ) INHERITS (zone) ;

    -- une table  une seule ligne pour indiquer s'il faut regnrer dhcpd.conf

    CREATE TABLE dhcp (
	generer INTEGER			-- 1 s'il faut regnerer la config
    ) ;

    INSERT INTO dhcp (generer) VALUES (0) ;

    -- les paramtres de configuration de la base
    CREATE TABLE config (
	clef		TEXT,		-- la clef de configuration
	valeur		TEXT,		-- valeur de la clef

	PRIMARY KEY (clef)
    ) ;


    -- les entres dans le log
    CREATE TABLE log (
	date		TIMESTAMP (0) WITHOUT TIME ZONE
				    DEFAULT CURRENT_TIMESTAMP
				    NOT NULL,
	subsys		TEXT NOT NULL,	-- sous-systme ("dns" ici)
	event		TEXT NOT NULL,	-- ajoutmachine, suppralias, etc.
	login		TEXT,		-- du correspondant
	ip		INET,		-- du correspondant
	msg		TEXT		-- message en clair
    ) ;

    -- fonctions

    -- valide un intervalle DHCP (min-max) par rapport aux droits du groupe
    -- $1 : idgrp
    -- $2 : dhcp min
    -- $3 : dhcp max
    CREATE OR REPLACE FUNCTION valide_dhcprange_grp (INTEGER, INET, INET)
		    RETURNS BOOLEAN AS '
	set min {}
	foreach o [split $2 "."] {
	    lappend min [format "%02x" $o]
	}
	set min [join $min ""]
	set min [expr 0x$min]
	set ipbin [expr 0x$min]

	set max {}
	foreach o [split $3 "."] {
	    lappend max [format "%02x" $o]
	}
	set max [join $max ""]
	set max [expr 0x$max]

	set r t
	for {set ipbin $min} {$ipbin <= $max} {incr ipbin} {
	    # Preparer la nouvelle adresse IP
	    set ip {}
	    set o $ipbin
	    for {set i 0} {$i < 4} {incr i} {
		set ip [linsert $ip 0 [expr $o & 0xff]]
		set o [expr $o >> 8]
	    }
	    set ip [join $ip "."]

	    # Tester la validite
	    spi_exec "SELECT valide_ip_grp (''$ip'', $1) AS v"

	    if {! [string equal $v "t"]} then {
		set r f
		break
	    }
	}
	return $r
	' LANGUAGE pltcl ;


    --------------------------------------------------------------------------
    -- fonction pour marquer l'espace d'adressage d'un CIDR, et caractriser
    -- chaque adresse IPv4 par une des valeurs suivantes
    --   0 : unavailable (broadcast addr, no right on addr, etc.)
    --   1 : not declared and not in a dhcp range
    --   2 : declared and not in a dhcp range
    --   3 : not declared and in a dhcp range
    --   4 : declared and in a dhcp range
    -- Cette fonction cre une table temporaire (allip) qui ne dure
    -- que le temps de la session postgresql. Cette table n'est pas visible
    -- des autres sessions.
    -- Comme cette fonction fait un parcours squentiel de l'espace d'adressage
    -- une valeur limite est indique pour ne pas surcharger le moteur
    -- PostgreSQL. Cette limite est indique par les scripts (cf www/bin/liste
    -- et www/bin/traitejout)
    --------------------------------------------------------------------------

    CREATE OR REPLACE FUNCTION markcidr (reseau CIDR, lim INTEGER, grp INTEGER)
	RETURNS void AS $$
	DECLARE
	    min INET ;
	    max INET ;
	    a INET ;
	BEGIN
	    min := INET (HOST (reseau)) ;
	    max := INET (HOST (BROADCAST (reseau))) ;

	    IF max - min - 2 > lim THEN
		RAISE EXCEPTION 'Too many addresses' ;
	    END IF ;

	    -- All this exception machinery is here since we can't use :
	    --    DROP TABLE IF EXISTS allip ;
	    -- It raises a notice exception, which prevents
	    -- script "ajout" to function
	    BEGIN
		DROP TABLE allip ;
	    EXCEPTION
		WHEN OTHERS THEN -- nothing
	    END ;

	    CREATE TEMPORARY TABLE allip (
		adr INET,
		avail INTEGER,
		    -- 0 : unavailable (broadcast addr, no right on addr, etc.)
		    -- 1 : not declared and not in a dhcp range
		    -- 2 : declared and not in a dhcp range
		    -- 3 : not declared and in a dhcp range
		    -- 4 : declared and in a dhcp range
		fqdn TEXT			-- if 2 or 4, then fqdn else NULL
	    ) ;

	    a := min ; 
	    WHILE a <= max LOOP
		INSERT INTO allip VALUES (a, 1) ;
		a := a + 1 ;
	    END LOOP ;

	    UPDATE allip
		SET fqdn = rr.nom || '.' || domaine.nom,
		    avail = 2
		FROM rr_ip, rr, domaine
		WHERE allip.adr = rr_ip.adr
		    AND rr_ip.idrr = rr.idrr
		    AND rr.iddom = domaine.iddom
		    ;

	    UPDATE allip
		SET avail = CASE
				WHEN avail = 1 THEN 3
				WHEN avail = 2 THEN 4
			    END
		FROM dhcprange
		WHERE (avail = 1 OR avail = 2)
		    AND adr >= dhcprange.min
		    AND adr <= dhcprange.max
		;

	    UPDATE allip SET avail = 0
		WHERE adr = min OR adr = max OR NOT valide_ip_grp (adr, grp) ;

	    RETURN ;

	END ;
	$$ LANGUAGE plpgsql ;

    --------------------------------------------------------------------------
    -- fonction pour rechercher des blocs d'adresses IP(v4) conscutives
    -- disponibles.
    -- (version pour postgresql 8.3, la version pour 8.4 aurait t plus
    -- lgante)
    --------------------------------------------------------------------------

    CREATE TYPE iprange_t AS (a INET, n INTEGER) ;

    CREATE OR REPLACE FUNCTION ipranges (reseau CIDR, lim INTEGER, grp INTEGER)
	RETURNS SETOF iprange_t AS $$
	DECLARE
	    inarange BOOLEAN ;
	    r RECORD ;
	    q iprange_t%ROWTYPE ;
	BEGIN
	    PERFORM markcidr (reseau, lim, grp) ;
	    inarange := FALSE ;
	    FOR r IN (SELECT adr, avail FROM allip ORDER BY adr)
	    LOOP
		IF inarange THEN
		    -- (q.a, q.n) is already a valid range
		    IF r.avail = 1 THEN
			q.n := q.n + 1 ;
		    ELSE
			RETURN NEXT q ;
			inarange := FALSE ;
		    END IF ;
		ELSE
		    -- not inside a range
		    IF r.avail = 1 THEN
			-- start a new range (q.a, q.n)
			q.a := r.adr ;
			q.n := 1 ;
			inarange := TRUE ;
		    END IF ;
		END IF ;
	    END LOOP ;
	    IF inarange THEN
		RETURN NEXT q ;
	    END IF ;
	    DROP TABLE allip ;
	    RETURN ;
	END ;
	$$ LANGUAGE plpgsql ;
EOF

##############################################################################
# Les droits pour les administrateurs de la base
##############################################################################

for root in $ROOT
do
    psql -q $BASE <<EOF
	GRANT ALL
	    ON 
		seq_domaine, domaine,
		seq_groupe, groupe, seq_corresp, corresp,
		seq_reseau, reseau, dr_reseau,
		seq_etablissement, etablissement,
		seq_communaute, communaute,
		dr_dom, dr_ip,
		seq_dhcprange, dhcprange,
		seq_dhcpprofil, dhcpprofil, dr_dhcpprofil,
		seq_rr, rr,
		rr_ip, rr_cname, rr_mx,
		seq_hinfo, hinfo,
		role_web, role_mail, dr_mbox, relais_dom,
		seq_zone, zone, zone_normale, zone_reverse4, zone_reverse6,
		dhcp,
		config, log
	    TO $root ;
EOF
done

##############################################################################
# Les droits pour l'interface Web (utilisateur nobody)
##############################################################################

for nobody in $NOBODY
do
    psql -q $BASE <<EOF
	GRANT SELECT
	    ON
		seq_domaine, domaine,
		seq_groupe, groupe, seq_corresp, corresp,
		seq_reseau, reseau, dr_reseau,
		seq_etablissement, etablissement,
		seq_communaute, communaute,
		dr_dom, dr_ip,
		seq_dhcprange, dhcprange,
		seq_dhcpprofil, dhcpprofil, dr_dhcpprofil,
		seq_rr, rr,
		rr_ip, rr_cname, rr_mx,
		seq_hinfo, hinfo,
		role_web, role_mail, dr_mbox, relais_dom,
		seq_zone, zone, zone_normale, zone_reverse4, zone_reverse6,
		dhcp,
		config, log
	    TO $nobody ;
	GRANT INSERT
	    ON
		domaine,
		groupe, corresp,
		reseau, dr_reseau,
		etablissement,
		communaute,
		dr_dom, dr_ip,
		dhcprange,
		dhcpprofil, dr_dhcpprofil,
		rr,
		rr_ip, rr_cname, rr_mx,
		hinfo,
		role_web, role_mail, dr_mbox, relais_dom,
		zone, zone_normale, zone_reverse4, zone_reverse6,
		dhcp,
		config, log
	    TO $nobody ;
	GRANT UPDATE
	    ON
		seq_domaine, domaine,
		seq_groupe, groupe, seq_corresp, corresp,
		seq_reseau, reseau, dr_reseau,
		seq_etablissement, etablissement,
		seq_communaute, communaute,
		dr_dom, dr_ip,
		seq_dhcprange, dhcprange,
		seq_dhcpprofil, dhcpprofil, dr_dhcpprofil,
		seq_rr, rr,
		rr_ip, rr_cname, rr_mx,
		seq_hinfo, hinfo,
		role_web, role_mail, dr_mbox, relais_dom,
		seq_zone, zone, zone_normale, zone_reverse4, zone_reverse6,
		dhcp,
		config, log
	    TO $nobody ;
	GRANT DELETE
	    ON
		domaine,
		corresp,
		reseau, dr_reseau,
		etablissement,
		communaute,
		dr_dom, dr_ip,
		dhcprange,
		dhcpprofil, dr_dhcpprofil,
		rr,
		rr_ip, rr_cname, rr_mx,
		hinfo,
		role_web, role_mail, dr_mbox, relais_dom,
		zone, zone_normale, zone_reverse4, zone_reverse6,
		dhcp,
		config, log
	    TO $nobody ;
EOF
done
