1------------------------------------------------------------------------------ 2-- Netmagis SQL functions 3------------------------------------------------------------------------------ 4 5 6------------------------------------------------------------------------------ 7-- Check a DHCP range against group permissions 8-- 9-- Input: 10-- - $1 : idgrp 11-- - $2 : dhcp min 12-- - $3 : dhcp max 13-- Output: 14-- - true (all addresses in DHCP range are allowed) or false 15-- 16-- History 17-- 200?/??/?? : pda : design 18-- 19 20 21-- check a DHCP range against group permissions 22CREATE OR REPLACE FUNCTION dns.check_dhcprange_grp (INTEGER, INET, INET) 23 RETURNS BOOLEAN AS ' 24 set min {} 25 foreach o [split $2 "."] { 26 lappend min [format "%02x" $o] 27 } 28 set min [join $min ""] 29 set min [expr 0x$min] 30 set ipbin [expr 0x$min] 31 32 set max {} 33 foreach o [split $3 "."] { 34 lappend max [format "%02x" $o] 35 } 36 set max [join $max ""] 37 set max [expr 0x$max] 38 39 set r t 40 for {set ipbin $min} {$ipbin <= $max} {incr ipbin} { 41 # Prepare the new IP address 42 set ip {} 43 set o $ipbin 44 for {set i 0} {$i < 4} {incr i} { 45 set ip [linsert $ip 0 [expr $o & 0xff]] 46 set o [expr $o >> 8] 47 } 48 set ip [join $ip "."] 49 50 # Check validity 51 spi_exec "SELECT dns.check_ip_grp (''$ip'', $1) AS v" 52 53 if {! [string equal $v "t"]} then { 54 set r f 55 break 56 } 57 } 58 return $r 59 ' LANGUAGE pltcl ; 60 61 62------------------------------------------------------------------------------ 63-- Classifies each IPv4 address in a network 64-- 65-- Input: 66-- - net: network address 67-- - lim: limit on the number of addresses classified 68-- - grp: group id 69-- Output: 70-- - table with columns: 71-- addr INET 72-- avail INTEGER (see below) 73-- fqdn TEXT 74-- 75-- Note: addresses are classified according to: 76-- 0 : unavailable (broadcast addr, no right on addr, etc.) 77-- 1 : not declared and not in a dhcp range 78-- 2 : declared and not in a dhcp range 79-- 3 : not declared and in a dhcp range 80-- 4 : declared and in a dhcp range 81-- This function creates a temporary table (allip) which only exists 82-- during the postgresql session lifetime. This table is internal to 83-- the session (other sessions cannot see it). 84-- Since this function performs a sequential traversal of IP range, 85-- a limit value must be given to not overload the PostgreSQL engine. 86-- 87-- History 88-- 200?/??/?? : pda : design 89-- 90 91CREATE OR REPLACE FUNCTION dns.mark_cidr (net CIDR, lim INTEGER, grp INTEGER) 92 RETURNS void AS $$ 93 DECLARE 94 min INET ; 95 max INET ; 96 a INET ; 97 BEGIN 98 min := INET (HOST (net)) ; 99 max := INET (HOST (BROADCAST (net))) ; 100 101 IF max - min - 2 > lim THEN 102 RAISE EXCEPTION 'Too many addresses' ; 103 END IF ; 104 105 -- All this exception machinery is here since we can't use : 106 -- DROP TABLE IF EXISTS allip ; 107 -- It raises a notice exception, which prevents 108 -- script "ajout" to function 109 BEGIN 110 DROP TABLE allip ; 111 EXCEPTION 112 WHEN OTHERS THEN -- nothing 113 END ; 114 115 CREATE TEMPORARY TABLE allip ( 116 addr INET, 117 avail INTEGER, 118 -- 0 : unavailable (broadcast addr, no right on addr, etc.) 119 -- 1 : not declared and not in a dhcp range 120 -- 2 : declared and not in a dhcp range 121 -- 3 : not declared and in a dhcp range 122 -- 4 : declared and in a dhcp range 123 fqdn TEXT -- if 2 or 4, then fqdn else NULL 124 ) ; 125 126 a := min ; 127 WHILE a <= max LOOP 128 INSERT INTO allip VALUES (a, 1) ; 129 a := a + 1 ; 130 END LOOP ; 131 132 UPDATE allip 133 SET fqdn = rr.name || '.' || domain.name, 134 avail = 2 135 FROM dns.rr_ip, dns.rr, dns.domain 136 WHERE allip.addr = rr_ip.addr 137 AND rr_ip.idrr = rr.idrr 138 AND rr.iddom = domain.iddom 139 ; 140 141 UPDATE allip 142 SET avail = CASE 143 WHEN avail = 1 THEN 3 144 WHEN avail = 2 THEN 4 145 END 146 FROM dns.dhcprange 147 WHERE (avail = 1 OR avail = 2) 148 AND addr >= dhcprange.min 149 AND addr <= dhcprange.max 150 ; 151 152 UPDATE allip SET avail = 0 153 WHERE addr = min OR addr = max OR NOT dns.check_ip_grp (addr, grp) ; 154 155 RETURN ; 156 157 END ; 158 $$ LANGUAGE plpgsql ; 159 160------------------------------------------------------------------------------ 161-- Search IPv4 address range for available blocks 162-- 163-- Input: 164-- - net: network address 165-- - lim: limit on the number of addresses classified 166-- - grp: group id 167-- Output: 168-- - table with columns: 169-- a INET -- starting address 170-- n INTEGER -- number of addresses in block 171-- 172-- Note: this is the PostgreSQL 8.3 version (the 8.4 version would have 173-- been more elegant) 174-- 175-- History 176-- 200?/??/?? : pda : design 177-- 178 179DROP TYPE IF EXISTS iprange_t ; 180CREATE TYPE iprange_t AS (a INET, n INTEGER) ; 181 182CREATE OR REPLACE FUNCTION dns.ipranges (net CIDR, lim INTEGER, grp INTEGER) 183 RETURNS SETOF iprange_t AS $$ 184 DECLARE 185 inarange BOOLEAN ; 186 r RECORD ; 187 q iprange_t%ROWTYPE ; 188 BEGIN 189 PERFORM dns.mark_cidr (net, lim, grp) ; 190 inarange := FALSE ; 191 FOR r IN (SELECT addr, avail FROM allip ORDER BY addr) 192 LOOP 193 IF inarange THEN 194 -- (q.a, q.n) is already a valid range 195 IF r.avail = 1 THEN 196 q.n := q.n + 1 ; 197 ELSE 198 RETURN NEXT q ; 199 inarange := FALSE ; 200 END IF ; 201 ELSE 202 -- not inside a range 203 IF r.avail = 1 THEN 204 -- start a new range (q.a, q.n) 205 q.a := r.addr ; 206 q.n := 1 ; 207 inarange := TRUE ; 208 END IF ; 209 END IF ; 210 END LOOP ; 211 IF inarange THEN 212 RETURN NEXT q ; 213 END IF ; 214 DROP TABLE allip ; 215 RETURN ; 216 END ; 217 $$ LANGUAGE plpgsql ; 218 219------------------------------------------------------------------------------ 220-- Set the generation flag for one or more zones. These functions 221-- are called from the corresponding trigger functions and set the 222-- generation flag for all modified zones. 223-- 224-- Input: 225-- - $1: IPv4/v6 address or domain id or RR id 226-- - $2: view id 227-- Output: 228-- - an unused integer value, just to be able to call sum() on result 229-- 230-- History 231-- 2002/??/?? : pda/jean : design 232-- 233 234-- called when an IPv4 address is modified ($1=addr, $2=idrr) 235CREATE OR REPLACE FUNCTION dns.gen_rev4 (INET, INTEGER) 236 RETURNS INTEGER AS $$ 237 BEGIN 238 UPDATE dns.zone_reverse4 AS z SET gen = 1 239 FROM dns.rr 240 WHERE $1 <<= selection 241 AND rr.idrr = $2 242 AND z.idview = rr.idview ; 243 RETURN 1 ; 244 END ; 245 $$ LANGUAGE 'plpgsql' ; 246 247-- called when an IPv6 address is modified ($1=addr, $2=idrr) 248CREATE OR REPLACE FUNCTION dns.gen_rev6 (INET, INTEGER) 249 RETURNS INTEGER AS $$ 250 BEGIN 251 UPDATE dns.zone_reverse6 AS z SET gen = 1 252 FROM dns.rr 253 WHERE $1 <<= selection 254 AND rr.idrr = $2 255 AND z.idview = rr.idview ; 256 RETURN 1 ; 257 END ; 258 $$ LANGUAGE 'plpgsql' ; 259 260-- ID of RR ($1=idrr) 261CREATE OR REPLACE FUNCTION dns.gen_norm_idrr (INTEGER) 262 RETURNS INTEGER AS $$ 263 BEGIN 264 UPDATE dns.zone_forward SET gen = 1 265 WHERE (selection, idview) = 266 ( 267 SELECT domain.name, rr.idview 268 FROM dns.domain, dns.rr 269 WHERE rr.idrr = $1 270 AND rr.iddom = domain.iddom 271 ) ; 272 RETURN 1 ; 273 END ; 274 $$ LANGUAGE 'plpgsql' ; 275 276-- ID of RR ($1=iddom, $2=idview) 277CREATE OR REPLACE FUNCTION dns.gen_norm_iddom (INTEGER, INTEGER) 278 RETURNS INTEGER AS $$ 279 BEGIN 280 UPDATE dns.zone_forward SET gen = 1 281 WHERE idview = $2 282 AND selection = ( 283 SELECT domain.name 284 FROM dns.domain 285 WHERE domain.iddom = $1 286 ) ; 287 RETURN 1 ; 288 END ; 289 $$ LANGUAGE 'plpgsql' ; 290 291-- utility function for the mod_relay trigger function 292-- called when a mail relay is modified ($1=iddom, $2=idrr of mx) 293CREATE OR REPLACE FUNCTION dns.gen_relay (INTEGER, INTEGER) 294 RETURNS INTEGER AS $$ 295 BEGIN 296 UPDATE dns.zone_forward SET gen = 1 297 WHERE selection = ( SELECT name FROM dns.domain WHERE iddom = $1 ) 298 AND idview = ( SELECT idview FROM dns.rr WHERE idrr = $2 ) 299 ; 300 RETURN 1 ; 301 END ; 302 $$ LANGUAGE 'plpgsql' ; 303 304------------------------------------------------------------------------------ 305-- Set the DHCP generation flag for one or more views. 306-- 307-- Input: 308-- - $1: RR id 309-- Output: 310-- - an unused integer value, just to be able to call sum() on result 311-- 312-- History 313-- 201?/??/?? : pda/jean : design 314-- 315 316CREATE OR REPLACE FUNCTION dns.gen_dhcp (INTEGER) 317 RETURNS INTEGER AS $$ 318 BEGIN 319 UPDATE dns.view SET gendhcp = 1 320 FROM dns.rr 321 WHERE rr.idrr = $1 322 AND rr.mac IS NOT NULL 323 AND view.idview = rr.idview ; 324 RETURN 1 ; 325 END ; 326 $$ LANGUAGE 'plpgsql' ; 327 328------------------------------------------------------------------------------ 329-- Trigger function called when an IP address is modified 330-- 331-- History 332-- 200?/??/?? : pda/jean : design 333-- 334 335CREATE OR REPLACE FUNCTION dns.mod_ip () 336 RETURNS trigger AS $$ 337 BEGIN 338 IF TG_OP = 'INSERT' 339 THEN 340 PERFORM sum (dns.gen_rev4 (NEW.addr, NEW.idrr)) ; 341 PERFORM sum (dns.gen_rev6 (NEW.addr, NEW.idrr)) ; 342 PERFORM sum (dns.gen_norm_idrr (NEW.idrr)) ; 343 PERFORM sum (dns.gen_dhcp (NEW.idrr)) ; 344 345 END IF ; 346 347 IF TG_OP = 'UPDATE' 348 THEN 349 PERFORM sum (dns.gen_rev4 (NEW.addr, NEW.idrr)) ; 350 PERFORM sum (dns.gen_rev4 (OLD.addr, OLD.idrr)) ; 351 PERFORM sum (dns.gen_rev6 (NEW.addr, NEW.idrr)) ; 352 PERFORM sum (dns.gen_rev6 (OLD.addr, OLD.idrr)) ; 353 PERFORM sum (dns.gen_norm_idrr (NEW.idrr)) ; 354 PERFORM sum (dns.gen_norm_idrr (OLD.idrr)) ; 355 PERFORM sum (dns.gen_dhcp (NEW.idrr)) ; 356 PERFORM sum (dns.gen_dhcp (OLD.idrr)) ; 357 END IF ; 358 359 IF TG_OP = 'DELETE' 360 THEN 361 PERFORM sum (dns.gen_rev4 (OLD.addr, OLD.idrr)) ; 362 PERFORM sum (dns.gen_rev6 (OLD.addr, OLD.idrr)) ; 363 PERFORM sum (dns.gen_norm_idrr (OLD.idrr)) ; 364 PERFORM sum (dns.gen_dhcp (OLD.idrr)) ; 365 END IF ; 366 367 RETURN NEW ; 368 END ; 369 $$ LANGUAGE 'plpgsql' ; 370 371------------------------------------------------------------------------------ 372-- Trigger function called when a CNAME or a MX is modified 373-- 374-- History 375-- 200?/??/?? : pda/jean : design 376-- 377 378CREATE OR REPLACE FUNCTION dns.mod_mxcname () 379 RETURNS trigger AS $$ 380 BEGIN 381 IF TG_OP = 'INSERT' 382 THEN 383 PERFORM sum (dns.gen_norm_idrr (NEW.idrr)) ; 384 END IF ; 385 386 IF TG_OP = 'UPDATE' 387 THEN 388 PERFORM sum (dns.gen_norm_idrr (NEW.idrr)) ; 389 PERFORM sum (dns.gen_norm_idrr (OLD.idrr)) ; 390 END IF ; 391 392 IF TG_OP = 'DELETE' 393 THEN 394 PERFORM sum (dns.gen_norm_idrr (OLD.idrr)) ; 395 END IF ; 396 397 RETURN NEW ; 398 END ; 399 $$ LANGUAGE 'plpgsql' ; 400 401------------------------------------------------------------------------------ 402-- Trigger function called when a RR is modified 403-- 404-- History 405-- 200?/??/?? : pda/jean : design 406-- 407 408-- modify RR and reverse zones for all IP addresses 409CREATE OR REPLACE FUNCTION dns.mod_rr () 410 RETURNS trigger AS $$ 411 BEGIN 412 -- IF TG_OP = 'INSERT' 413 -- THEN 414 -- no need to regenerate anything since no rr_* has 415 -- been linked to this rr yet 416 -- END IF ; 417 418 IF TG_OP = 'UPDATE' 419 THEN 420 PERFORM sum (dns.gen_norm_iddom (NEW.iddom, NEW.idview)) 421 ; 422 PERFORM sum (dns.gen_norm_iddom (OLD.iddom, OLD.idview)) 423 ; 424 PERFORM sum (dns.gen_rev4 (rr_ip.addr, NEW.idrr)) 425 FROM dns.rr_ip WHERE rr_ip.idrr = NEW.idrr ; 426 PERFORM sum (dns.gen_rev6 (rr_ip.addr, NEW.idrr)) 427 FROM dns.rr_ip WHERE rr_ip.idrr = NEW.idrr ; 428 PERFORM sum (dns.gen_dhcp (NEW.idrr)) 429 ; 430 -- no need to regenerate reverse/dhcp for old rr since 431 -- IP addresses did not change 432 END IF ; 433 434 -- IF TG_OP = 'DELETE' 435 -- THEN 436 -- no need to regenerate anything since all rr_* have 437 -- already been removed before 438 -- END IF ; 439 440 RETURN NEW ; 441 END ; 442 $$ LANGUAGE 'plpgsql' ; 443 444------------------------------------------------------------------------------ 445-- Trigger function called when a mail relay is modified 446-- 447-- History 448-- 200?/??/?? : pda/jean : design 449-- 450 451CREATE OR REPLACE FUNCTION dns.mod_relay () 452 RETURNS trigger AS $$ 453 BEGIN 454 IF TG_OP = 'INSERT' 455 THEN 456 PERFORM sum (dns.gen_relay (NEW.iddom, NEW.mx)) ; 457 END IF ; 458 459 IF TG_OP = 'UPDATE' 460 THEN 461 PERFORM sum (dns.gen_relay (NEW.iddom, NEW.mx)) ; 462 PERFORM sum (dns.gen_relay (OLD.iddom, OLD.mx)) ; 463 END IF ; 464 465 IF TG_OP = 'DELETE' 466 THEN 467 PERFORM sum (dns.gen_relay (OLD.iddom, OLD.mx)) ; 468 END IF ; 469 470 RETURN NEW ; 471 END ; 472 $$ LANGUAGE 'plpgsql' ; 473 474------------------------------------------------------------------------------ 475-- Trigger function called when a zone is modified 476-- 477-- History 478-- 200?/??/?? : pda/jean : design 479-- 480 481CREATE OR REPLACE FUNCTION dns.mod_zone () 482 RETURNS TRIGGER AS $$ 483 BEGIN 484 IF NEW.prologue <> OLD.prologue 485 OR NEW.rrsup <> OLD.rrsup 486 OR NEW.selection <> OLD.selection 487 THEN 488 NEW.gen := 1 ; 489 END IF ; 490 RETURN NEW ; 491 END ; 492 $$ LANGUAGE 'plpgsql' ; 493 494------------------------------------------------------------------------------ 495-- Trigger function called when a DHCP parameter (network, range or profile) 496-- is modified 497-- 498-- History 499-- 200?/??/?? : pda/jean : design 500-- 501 502CREATE OR REPLACE FUNCTION dns.mod_dhcp () 503 RETURNS TRIGGER AS $$ 504 BEGIN 505 UPDATE dns.view SET gendhcp = 1 ; 506 RETURN NEW ; 507 END ; 508 $$ LANGUAGE 'plpgsql' ; 509 510------------------------------------------------------------------------------ 511-- Check access rights to an IP address 512-- 513-- Input: 514-- - $1: IPv4/v6 address to test 515-- - $2: group id or user id 516-- Output: 517-- - true if access is allowed 518-- 519-- History 520-- 2002/??/?? : pda/jean : design 521-- 522 523CREATE OR REPLACE FUNCTION dns.check_ip_cor (INET, INTEGER) 524 RETURNS BOOLEAN AS $$ 525 BEGIN 526 RETURN dns.check_ip_grp ($1, idgrp) FROM global.nmuser WHERE idcor = $2 ; 527 END ; 528 $$ LANGUAGE 'plpgsql' ; 529 530CREATE OR REPLACE FUNCTION dns.check_ip_grp (INET, INTEGER) 531 RETURNS BOOLEAN AS $$ 532 BEGIN 533 RETURN ($1 <<= ANY (SELECT addr FROM dns.p_ip 534 WHERE allow_deny = 1 AND idgrp = $2) 535 AND NOT $1 <<= ANY (SELECT addr FROM dns.p_ip 536 WHERE allow_deny = 0 AND idgrp = $2) 537 ) ; 538 END ; 539 $$ LANGUAGE 'plpgsql' ; 540 541------------------------------------------------------------------------------ 542-- Trigger function called when a vlan is modified 543-- 544-- History 545-- 200?/??/?? : pda/jean : design 546-- 547 548CREATE OR REPLACE FUNCTION topo.mod_vlan () 549 RETURNS trigger AS $$ 550 BEGIN 551 INSERT INTO topo.modeq (eq) VALUES ('_vlan') ; 552 RETURN NEW ; 553 END ; 554 $$ LANGUAGE 'plpgsql' ; 555 556------------------------------------------------------------------------------ 557-- Trigger function called when an equipment is modified 558-- 559-- History 560-- 200?/??/?? : pda/jean : design 561-- 562 563CREATE OR REPLACE FUNCTION topo.mod_routerdb () 564 RETURNS trigger AS $$ 565 BEGIN 566 INSERT INTO topo.modeq (eq) VALUES ('_routerdb') ; 567 RETURN NEW ; 568 END ; 569 $$ LANGUAGE 'plpgsql' ; 570 571------------------------------------------------------------------------------ 572-- Reduce a string to a soundex code in order to find approximate 573-- names 574-- 575-- Input: 576-- - $1: string to reduce 577-- Output: 578-- - soundex 579-- 580-- History 581-- 200?/??/?? : pda : design 582-- 583 584CREATE FUNCTION pgauth.soundex (TEXT) 585 RETURNS TEXT AS ' 586 array set soundexFrenchCode { 587 a 0 b 1 c 2 d 3 e 0 f 9 g 7 h 0 i 0 j 7 k 2 l 4 m 5 588 n 5 o 0 p 1 q 2 r 6 s 8 t 3 u 0 v 9 w 9 x 8 y 0 z 8 589 } 590 set accentedFrenchMap { 591 é e ë e ê e è e É E Ë E Ê E È E 592 ä a â a à a Ä A Â A À A 593 ï i î i Ï I Î I 594 ö o ô o Ö O Ô O 595 ü u û u ù u Ü U Û U Ù U 596 ç ss Ç SS 597 } 598 set key "" 599 600 # Map accented characters 601 set TempIn [string map $accentedFrenchMap $1] 602 603 # Only use alphabetic characters, so strip out all others 604 # also, soundex index uses only lower case chars, so force to lower 605 606 regsub -all {[^a-z]} [string tolower $TempIn] {} TempIn 607 if {$TempIn eq ""} then { 608 return Z000 609 } 610 set last [string index $TempIn 0] 611 set key [string toupper $last] 612 set last $soundexFrenchCode($last) 613 614 # Scan rest of string, stop at end of string or when the key is full 615 616 set count 1 617 set MaxIndex [string length $TempIn] 618 619 for {set index 1} {(($count < 4) && ($index < $MaxIndex))} {incr index } { 620 set chcode $soundexFrenchCode([string index $TempIn $index]) 621 # Fold together adjacent letters sharing the same code 622 if {$last ne $chcode} then { 623 set last $chcode 624 # Ignore code==0 letters except as separators 625 if {$last != 0} then { 626 set key $key$last 627 incr count 628 } 629 } 630 } 631 return [string range ${key}0000 0 3] 632 ' LANGUAGE 'pltcl' WITH (isStrict) ; 633 634------------------------------------------------------------------------------ 635-- Trigger function: computes soundex for name and first name 636-- each time a name or first name is modified. 637-- 638-- History 639-- 200?/??/?? : pda : design 640-- 641 642CREATE FUNCTION pgauth.add_soundex () 643 RETURNS TRIGGER AS ' 644 BEGIN 645 NEW.phlast := pgauth.SOUNDEX (NEW.lastname) ; 646 NEW.phfirst := pgauth.SOUNDEX (NEW.firstname) ; 647 RETURN NEW ; 648 END ; 649 ' LANGUAGE 'plpgsql' ; 650