1# 2# TCL library for Netmagis 3# 4# 5# History 6# 2002/03/27 : pda/jean : design 7# 2002/05/23 : pda/jean : add info-groupe 8# 2004/01/14 : pda/jean : add IPv6 9# 2004/08/04 : pda/jean : aadd MAC 10# 2004/08/06 : pda/jean : extension of network access rights 11# 2006/01/26 : jean : bug fix in check-authorized-host (case ip EXIST) 12# 2006/01/30 : jean : alias message in check-authorized-host 13# 2010/11/29 : pda : i18n 14# 2010/12/17 : pda : reworked installation and parameters 15# 2011/01/02 : pda : integration of libauth in libdns 16# 2011/07/29 : pda : renamed to libnetmagis 17# 18 19############################################################################## 20# Configuration file processing 21############################################################################## 22 23# 24# Read configuration file 25# 26# Input: 27# - parameters: 28# - file : configuration file 29# Output: 30# - none (program ends if an error is encountered) 31# 32# History 33# 2010/12/17 : pda : design 34# 2013/08/29 : pda/jean : reset the internal representation before file read 35# 2014/02/26 : pda/jean : add the pseudo-parameter _conffile 36# 2014/02/26 : pda/jean : add the pseudo-parameter _version 37# 38 39proc read-local-conf-file {file} { 40 global netmagisconf 41 42 if {[catch {set fd [open "$file" "r"]} msg]} then { 43 puts stderr "Cannot open configuration file '$file'" 44 exit 1 45 } 46 set lineno 1 47 set errors false 48 array unset netmagisconf 49 while {[gets $fd line] >= 0} { 50 regsub {#.*} $line {} line 51 regsub {\s*$} $line {} line 52 if {$line ne ""} then { 53 if {[regexp {(\S+)\s+"(.*)"} $line m key val]} then { 54 set netmagisconf($key) $val 55 } elseif {[regexp {(\S+)\s+(.*)} $line m key val]} then { 56 set netmagisconf($key) $val 57 } else { 58 puts stderr "$file($lineno): unrecognized line $line" 59 set errors true 60 } 61 } 62 incr lineno 63 } 64 close $fd 65 if {$errors} then { 66 exit 1 67 } 68 set netmagisconf(_conffile) $file 69 set netmagisconf(_version) "%NMVERSION%" 70} 71 72# 73# Get configuration key 74# 75# Input: 76# - parameters: 77# - key : configuration key 78# Output: 79# - return value: configuration value or empty string 80# 81# History 82# 2010/12/17 : pda : design 83# 2010/12/19 : pda : empty string if key is not found 84# 85 86proc get-local-conf {key} { 87 global netmagisconf 88 89 if {[info exists netmagisconf($key)]} then { 90 set v $netmagisconf($key) 91 } else { 92 set v "" 93 } 94 return $v 95} 96 97# 98# Get database handle 99# 100# Input: 101# - parameters: 102# - prefix : prefix for configuration keys (e.g. db for dbhost/dbname/...) 103# Output: 104# - return value: conninfo script for pg_connect 105# 106# History 107# 2010/12/17 : pda : design 108# 2011/01/21 : pda : add port specification 109# 2013/02/08 : pda/jean : fix bug in values containing special characters 110# 111 112proc get-conninfo {prefix} { 113 set conninfo {} 114 foreach f {{host host} {port port} {dbname name} 115 {user user} {password password}} { 116 lassign $f connkey suffix 117 set v [get-local-conf "$prefix$suffix"] 118 regsub {['\\]} $v {\\&} v 119 lappend conninfo "$connkey='$v'" 120 } 121 return [join $conninfo " "] 122} 123 124############################################################################## 125# Library initialization 126############################################################################## 127 128read-local-conf-file %CONFFILE% 129 130lappend auto_path [get-local-conf "pkgtcl"] 131set debug [get-local-conf "debug"] 132 133package require msgcat ;# tcl 134namespace import ::msgcat::* 135 136package require snit ;# tcllib 137package require ip ;# tcllib 138package require md5 ;# tcllib 139package require md5crypt ;# tcllib 140package require uuid ;# tcllib 141 142package require webapp 143package require pgsql 144package require arrgen 145 146############################################################################## 147# Library parameters 148############################################################################## 149 150# 151# Authentication pages 152# 153 154set libconf(page-login) "login.html" 155set libconf(next-login) "login" 156 157set libconf(token-length) 64 158 159# 160# Various table specifications 161# 162 163set libconf(tabperm) { 164 global { 165 chars {10 normal} 166 align {left} 167 botbar {yes} 168 columns {75 25} 169 } 170 pattern Normal { 171 vbar {yes} 172 column { } 173 vbar {yes} 174 column { } 175 vbar {yes} 176 } 177} 178 179set libconf(tabdreq) { 180 global { 181 chars {10 normal} 182 align {left} 183 botbar {yes} 184 columns {20 80} 185 } 186 pattern PermEq { 187 vbar {yes} 188 column { } 189 vbar {yes} 190 column { 191 chars {bold} 192 format {lines} 193 } 194 vbar {yes} 195 } 196} 197 198set libconf(tabnetworks) { 199 global { 200 chars {10 normal} 201 align {left} 202 botbar {yes} 203 columns {15 35 15 35} 204 } 205 pattern Network { 206 vbar {yes} 207 column { 208 align {center} 209 chars {14 bold} 210 multicolumn {4} 211 } 212 vbar {yes} 213 } 214 pattern Normal4 { 215 vbar {yes} 216 column { } 217 vbar {yes} 218 column { 219 chars {bold} 220 } 221 vbar {yes} 222 column { } 223 vbar {yes} 224 column { 225 chars {bold} 226 } 227 vbar {yes} 228 } 229 pattern Perm { 230 vbar {yes} 231 column { } 232 vbar {yes} 233 column { 234 multicolumn {3} 235 chars {bold} 236 format {lines} 237 } 238 vbar {yes} 239 } 240} 241 242set libconf(tabdomains) { 243 global { 244 chars {10 normal} 245 align {left} 246 botbar {yes} 247 columns {50 50} 248 } 249 pattern Title { 250 chars {gras} 251 vbar {yes} 252 column { } 253 vbar {yes} 254 column { } 255 vbar {yes} 256 } 257 pattern Normal { 258 vbar {yes} 259 column { } 260 vbar {yes} 261 column { } 262 vbar {yes} 263 } 264} 265 266set libconf(tabviews) $libconf(tabdomains) 267 268set libconf(tabdhcpprofile) { 269 global { 270 chars {10 normal} 271 align {left} 272 botbar {yes} 273 columns {25 75} 274 } 275 pattern DHCP { 276 vbar {yes} 277 column { } 278 vbar {no} 279 column { 280 format {lines} 281 } 282 vbar {yes} 283 } 284} 285 286set libconf(tabl2only) { 287 global { 288 chars {10 normal} 289 align {left} 290 botbar {yes} 291 columns {100} 292 } 293 pattern Normal { 294 vbar {yes} 295 column { 296 format {lines} 297 } 298 vbar {yes} 299 } 300} 301 302set libconf(tabmachine) { 303 global { 304 chars {10 normal} 305 align {left} 306 botbar {yes} 307 columns {20 80} 308 } 309 pattern Normal { 310 vbar {yes} 311 column { } 312 vbar {yes} 313 column { 314 format {raw} 315 } 316 vbar {yes} 317 } 318} 319 320set libconf(tabwtmp) { 321 global { 322 chars {10 normal} 323 align {left} 324 botbar {yes} 325 columns {15 25 25 25 25} 326 } 327 pattern Title { 328 vbar {yes} 329 column { chars {bold} } 330 vbar {yes} 331 column { chars {bold} } 332 vbar {yes} 333 column { chars {bold} } 334 vbar {yes} 335 column { chars {bold} } 336 vbar {yes} 337 column { chars {bold} } 338 vbar {yes} 339 } 340 pattern Normal { 341 vbar {yes} 342 column { } 343 vbar {yes} 344 column { } 345 vbar {yes} 346 column { } 347 vbar {yes} 348 column { } 349 vbar {yes} 350 column { } 351 vbar {yes} 352 } 353} 354 355set libconf(tabuser) { 356 global { 357 chars {10 normal} 358 align {left} 359 botbar {yes} 360 columns {20 80} 361 } 362 pattern Normal { 363 vbar {yes} 364 column { } 365 vbar {yes} 366 column { 367 chars {gras} 368 } 369 vbar {yes} 370 } 371} 372 373set libconf(tabeqstatus) { 374 global { 375 chars {10 normal} 376 align {left} 377 botbar {yes} 378 columns {20 10 20 50} 379 } 380 pattern Title4 { 381 chars {gras} 382 vbar {yes} 383 column { } 384 vbar {yes} 385 column { } 386 vbar {yes} 387 column { } 388 vbar {yes} 389 column { } 390 vbar {yes} 391 } 392 pattern Normal4 { 393 vbar {yes} 394 column { } 395 vbar {yes} 396 column { } 397 vbar {yes} 398 column { } 399 vbar {yes} 400 column { } 401 vbar {yes} 402 } 403} 404 405 406set libconf(extractcoll) "extractcoll %s" 407set libconf(extracteq) "extracteq %s %s" 408 409# Cisco aironet frequency conversion table 410array set libconf { 411 freq:2412 1 412 freq:2417 2 413 freq:2422 3 414 freq:2427 4 415 freq:2432 5 416 freq:2437 6 417 freq:2442 7 418 freq:2447 8 419 freq:2452 9 420 freq:2457 10 421 freq:2462 11 422} 423 424# Authorised characters for vlan name 425set libconf(vlan-chars) {/+. a-zA-Z0-9()<>_-} 426 427############################################################################## 428# Netmagis application framework 429############################################################################## 430 431# 432# Netmagis access class 433# 434# This class is a simple way to initialize the whole context of all 435# Netmagis programs (CGI scripts, daemons, command line utilities). 436# 437# Methods: 438# cgi-register 439# register a CGI script and conditions to execute it 440# cgi-dispatch 441# dispatch execution to a registered CGI script 442# init-script 443# initialize context for an autonomous program (not CGI) 444# locale 445# set current locale 446# end 447# properly close access to application and to database 448# nextprog, nextargs 449# return next action (prog and args), i.e. page to come back when 450# current action (travel in the application) is finished 451# euid 452# returns the effective login and id of user 453# urlset 454# register a named URL as a path and arguments. These components 455# will be used in the output page, or with the urlget method 456# urladd 457# adds an argument to a registered named URL 458# urlsetnext 459# adds a specified next action (see nextprog/nextargs) to a 460# registered named URL 461# urladdnext 462# adds the current next action (see nextprog/nextargs) to a 463# registered named URL 464# urlsubst 465# returns a substitution list (see ::webapp::file-subst) with all 466# registered URLs 467# urlget 468# returns (and de-register) a named URL 469# module 470# sets the current module, used for the links menu 471# error 472# returns an error page and close access to application 473# errimg 474# returns an error image and close access to application 475# result 476# returns a page and close access to application 477# writelog 478# write a log message in the log system 479# dblock, dbabort, dbcommit 480# database locking/unlocking operations 481# 482# History 483# 2001/06/18 : pda : design 484# 2002/12/26 : pda : update and usage 485# 2003/05/13 : pda/jean : integration in netmagis and auth class usage 486# 2007/10/05 : pda/jean : adaptation to "authuser" and "authbase" objects 487# 2007/10/26 : jean : add log 488# 2010/10/25 : pda : add dnsconfig 489# 2010/11/05 : pda : use a snit object 490# 2010/11/09 : pda : add init-script 491# 2010/11/29 : pda : i18n 492# 2010/12/21 : pda/jean : add version in class 493# 2011/02/18 : pda : add scriptmode 494# 2012/01/02 : pda : add errimg 495# 496 497snit::type ::netmagis { 498 # Netmagis version 499 variable version "%NMVERSION%" 500 501 # cgi script dispatching (see cgi-register) 502 # critform : list of field names 503 # critscript : list {{crit form script} {crit form script} ...} 504 variable critform {} 505 variable critscript {} 506 507 # database handle 508 variable db "" 509 510 # mode : script, cgi, daemon 511 variable scriptmode "" 512 513 # in script or daemon mode, name of executing program 514 variable scriptargv0 515 516 # locale in use : either specified by browser, or specified by user 517 variable locale "C" 518 # locale specified by browser 519 variable blocale "C" 520 # all available locales. Order is not important. 521 variable avlocale {fr en} 522 523 # log access 524 variable log 525 526 # uid, and effective uid 527 variable uid "" 528 variable euid "" 529 variable eidcor -1 530 531 # HTML error page 532 variable errorpage "error.html" 533 534 # HTML home page 535 variable homepage -array { 536 :anon index 537 :dns start 538 :admin admindex 539 :pgauth pgauth 540 :mac macindex 541 :topo eq 542 } 543 544 # in order to come back from a travel in the Netmagis application 545 variable dnextprog "" 546 variable dnextargs "" 547 548 # URL declared in the scripts 549 # urltab(<name>) = {path {key val} {key val} {key val...}} 550 # <name> = %[A-Z0-9]+% or "" for a temporary URL 551 # urltab(<name>:nextprog) = <nextprog> or empty string 552 # urltab(<name>:nextargs) = <nextargs> (if <nextprog> != empty string) 553 variable urltab -array {} 554 555 # where are we in the application? 556 # authorized values: dns topo admin 557 variable curmodule "" 558 559 # current capacities (depending on user access rights or application 560 # installation/parametrization) 561 # possible values: admin dns topo 562 variable curcap {} 563 564 # Links menu 565 # This array has a tree structure: 566 # tab(:<module>) {{<element>|:<module> <cap>}..{<element>|:<module> <cap>}} 567 # tab(<element>) {<url> <desc>} 568 # 569 # The first type gives display order for a module 570 # - a module is one of the values of the "curmodule" variable, 571 # or a reference from another module (in this array) 572 # - each element or module is displayed only if the condition 573 # "cap" (capacity) is true for this user. Special "always" 574 # capacity means that this element or module is always 575 # displayed. 576 # - if a module is mentionned in the list, this module is 577 # recursively searched (which gives the tree structure, 578 # elements are the terminal nodes) 579 # The second type gives the display of a particular element. 580 variable links -array { 581 :anon { 582 } 583 :dns { 584 {start always} 585 {net always} 586 {add always} 587 {del always} 588 {mod always} 589 {mail always} 590 {dhcprange always} 591 {search always} 592 {whereami always} 593 {topotitle topo} 594 {passwd pgauth} 595 {mactitle mac} 596 {admtitle admin} 597 } 598 start {start Welcome} 599 net {net Consult} 600 add {add Add} 601 del {del Delete} 602 mod {mod Modify} 603 mail {mail {Mail roles}} 604 dhcprange {dhcp {DHCP ranges}} 605 passwd {pgapasswd Password} 606 search {search Search} 607 whereami {search?q=_ {Where am I?}} 608 topotitle {eq Topology} 609 mactitle {macindex Mac} 610 admtitle {admindex Admin} 611 :topo { 612 {eq always} 613 {l2 always} 614 {l3 always} 615 {genl topogenl} 616 {topotop admin} 617 {dnstitle dns} 618 {mactitle mac} 619 {admtitle admin} 620 } 621 eq {eq Equipments} 622 l2 {l2 Vlans} 623 l3 {l3 Networks} 624 dnstitle {start DNS/DHCP} 625 genl {genl {Link number}} 626 :admin { 627 {admtitle always} 628 {pgatitle authadmin} 629 {admlmx always} 630 {lnet always} 631 {lusers always} 632 {search always} 633 {whonow always} 634 {wholast always} 635 {modorg always} 636 {modcomm always} 637 {modhinfo always} 638 {modnetwork always} 639 {moddomain always} 640 {admmrel always} 641 {admmx always} 642 {modview always} 643 {modzone always} 644 {modzone4 always} 645 {modzone6 always} 646 {moddhcpprof always} 647 {modvlan always} 648 {modeqtype always} 649 {modeq always} 650 {modconfcmd always} 651 {moddotattr always} 652 {admgrp always} 653 {admzgen always} 654 {admpar always} 655 {statuser always} 656 {statorg always} 657 {topotop topo} 658 {dnstitle dns} 659 {topotitle topo} 660 {mactitle mac} 661 } 662 pgatitle {pgaindex {Internal Auth}} 663 admlmx {admlmx {List MX}} 664 lnet {lnet {List networks}} 665 lusers {lusers {List users}} 666 whonow {who?action=now {Connected users}} 667 wholast {who?action=last {Last connections}} 668 modorg {admref?type=org {Modify organizations}} 669 modcomm {admref?type=comm {Modify communities}} 670 modhinfo {admref?type=hinfo {Modify machine types}} 671 modnetwork {admref?type=net {Modify networks}} 672 moddomain {admref?type=domain {Modify domains}} 673 admmrel {admmrel {Modify mailhost}} 674 admmx {admmx {Modify MX}} 675 modview {admref?type=view {Modify views}} 676 modzone {admref?type=zone {Modify zones}} 677 modzone4 {admref?type=zone4 {Modify reverse IPv4 zones}} 678 modzone6 {admref?type=zone6 {Modify reverse IPv6 zones}} 679 moddhcpprof {admref?type=dhcpprof {Modify DHCP profiles}} 680 modvlan {admref?type=vlan {Modify Vlans}} 681 modeqtype {admref?type=eqtype {Modify equipment types}} 682 modeq {admref?type=eq {Modify equipments}} 683 modconfcmd {admref?type=confcmd {Modify configuration commands}} 684 moddotattr {admref?type=dotattr {Modify Graphviz attributes}} 685 admgrp {admgrp {Modify users and groups}} 686 admzgen {admzgen {Force zone generation}} 687 admpar {admpar {Application parameters}} 688 statuser {statuser {Statistics by user}} 689 statorg {statorg {Statistics by organization}} 690 topotop {topotop {Topod status}} 691 :mac { 692 {macindex always} 693 {mac always} 694 {ipinact always} 695 {macstat always} 696 {dnstitle dns} 697 {topotitle topo} 698 {admtitle admin} 699 } 700 macindex {macindex {MAC index}} 701 mac {mac {MAC search}} 702 ipinact {ipinact {Inactive addresses}} 703 macstat {macstat {MAC stats}} 704 :pgauth { 705 {admtitle always} 706 {pgatitle authadmin} 707 {pgaalst authadmin} 708 {pgaaprn authadmin} 709 {pgaaadd authadmin} 710 {pgaamod authadmin} 711 {pgaadel authadmin} 712 {pgaapasswd authadmin} 713 {pgarlst authadmin} 714 {pgaradd authadmin} 715 {pgarmod authadmin} 716 {pgardel authadmin} 717 {dnstitle dns} 718 {topotitle topo} 719 {mactitle mac} 720 } 721 pgaalst {pgaacc?action=list {List accounts}} 722 pgaaprn {pgaacc?action=print {Print accounts}} 723 pgaaadd {pgaacc?action=add {Add account}} 724 pgaamod {pgaacc?action=mod {Modify account}} 725 pgaadel {pgaacc?action=del {Remove account}} 726 pgaapasswd {pgaacc?action=passwd {Change account password}} 727 pgarlst {pgarealm?action=list {List realms}} 728 pgaradd {pgarealm?action=add {Add realm}} 729 pgarmod {pgarealm?action=mod {Modify realm}} 730 pgardel {pgarealm?action=del {Remove realm}} 731 } 732 733 # 734 # Links for the session menu 735 # 736 737 variable sessionlinks -array { 738 login login 739 logout login?logout=yes 740 profile profile 741 } 742 743 744 ########################################################################### 745 # Internal procedures 746 ########################################################################### 747 748 # 749 # Database initialization 750 # 751 # Input: 752 # - selfs : current object 753 # - _dbfd : database handle, in return 754 # 755 # Output: 756 # - return value: empty string or error message 757 # 758 759 proc init-database {selfns _dbfd} { 760 upvar $_dbfd dbfd 761 762 # 763 # Access to Netmagis database 764 # 765 766 set conninfo [get-conninfo "dnsdb"] 767 if {[catch {set dbfd [pg_connect -conninfo $conninfo]} msg]} then { 768 return [mc "Error accessing database: %s" $msg] 769 } 770 771 # 772 # Access to configuration parameters (stored in the database) 773 # 774 775 config ::dnsconfig 776 dnsconfig setdb $dbfd 777 778 # 779 # Check compatibility with database schema version 780 # - empty string : pre-2.2 schema 781 # - non empty string : integer containing schema version 782 # Netmagis version (x.y.... => xy) must match schema version. 783 # 784 785 # get code version (from top-level Makefile) 786 if {! [regsub {^(\d+)\.(\d+).*} $version {\1\2} nver]} then { 787 return [mc "Internal error: Netmagis version number '%s' unrecognized" $version] 788 } 789 790 # get schema version (from database) 791 if {[catch {dnsconfig get "schemaversion"} sver]} then { 792 set sver "" 793 } 794 795 if {$sver eq ""} then { 796 return [mc "Database schema is too old. See http://netmagis.org/upgrade.html"] 797 } elseif {$sver < $nver} then { 798 return [mc "Database schema is too old. See http://netmagis.org/upgrade.html"] 799 } elseif {$sver > $nver} then { 800 return [mc {Database schema '%1$s' is not yet recognized by Netmagis %2$s} $sver $version] 801 } 802 803 # 804 # Log initialization 805 # 806 807 set log [::webapp::log create %AUTO% \ 808 -subsys netmagis \ 809 -method opened-postgresql \ 810 -medium [list "db" $dbfd table global.log] \ 811 ] 812 813 # 814 # Access to database is initialized 815 # 816 817 set db $dbfd 818 819 return "" 820 } 821 822 # 823 # Common initialization work 824 # 825 # Input: 826 # - selfs : current object 827 # - dbfd : database handle 828 # - login : user's login 829 # - anon : "anon" (don't fetch identity in auth database) or "id" (fetch) 830 # - usedefuser : use default user name if login is not found 831 # - _tabuid : array containing, in return, user's characteristics 832 # (login, password, lastname, firstname, mail, phone, fax, 833 # mobile, addr, idcor, idgrp, present) 834 # 835 # Output: 836 # - return value: empty string or error message 837 # 838 839 proc init-common {selfns dbfd login anon usedefuser _tabuid} { 840 global ah 841 upvar $_tabuid tabuid 842 843 set uid $login 844 set euid $login 845 846 # 847 # Access to authentification mechanism (database or LDAP) 848 # 849 850 set am [dnsconfig get "authmethod"] 851 switch $am { 852 pgsql { 853 set m {-method opened-postgresql} 854 lappend m "-db" $dbfd 855 } 856 casldap - 857 ldap { 858 foreach v {ldapurl ldapbinddn ldapbindpw ldapbasedn 859 ldapsearchlogin ldapattrlogin 860 ldapattrname ldapattrgivenname ldapattrmail 861 ldapattrphone ldapattrmobile ldapattrfax 862 ldapattraddr} { 863 set $v [dnsconfig get $v] 864 } 865 set m {-method ldap} 866 lappend m "-db" [list \ 867 "url" $ldapurl \ 868 "binddn" $ldapbinddn \ 869 "bindpw" $ldapbindpw \ 870 "base" $ldapbasedn \ 871 "searchuid" $ldapsearchlogin \ 872 ] 873 lappend m "-attrmap" [list \ 874 "login" $ldapattrlogin \ 875 "lastname" $ldapattrname \ 876 "firstname" $ldapattrgivenname \ 877 "maill" $ldapattrmail \ 878 "phone" $ldapattrphone \ 879 "mobile" $ldapattrmobile \ 880 "fax" $ldapattrfax \ 881 "addr" $ldapattraddr \ 882 ] 883 } 884 default { 885 return [mc "Unrecognized authentication method '%s'" $am] 886 } 887 } 888 889 switch $anon { 890 id { 891 set ah [::webapp::authbase create %AUTO%] 892 $ah configurelist $m 893 } 894 anon { 895 set ah "" 896 } 897 } 898 899 # 900 # Reads all user's characteristics. If this user is not 901 # marked "present" in the database, get him out! 902 # 903 904 set n [read-user $dbfd $login tabuid msg] 905 switch $n { 906 0 { 907 if {$usedefuser} then { 908 set login [dnsconfig get "defuser"] 909 910 set uid $login 911 set euid $login 912 set n [read-user $dbfd $login tabuid msg] 913 } 914 # IF user is not found 915 # OR (able to use default user AND default user is not found) 916 if {$n != 1} then { 917 return $msg 918 } 919 } 920 1 { 921 # Set at least the login 922 set tabuid(login) $login 923 } 924 default { 925 return $msg 926 } 927 } 928 if {! $tabuid(present)} then { 929 return [mc "User '%s' not authorized" $login] 930 } 931 set eidcor $tabuid(idcor) 932 933 # 934 # Initializes user object 935 # 936 ::nmuser create ::u 937 u setdb $dbfd 938 u setlogin $login 939 940 # 941 # Access to Netmagis is now initialized 942 # 943 944 return "" 945 } 946 947 # 948 # Builds up an URL 949 # 950 # Input: 951 # - _urltab : name of an array containing : 952 # urltab($name): the list {path {key val} {key val} ...} 953 # urltab($name:nextprog) program 954 # urltab($name:nextargs) arguments 955 # - name : index in urltab 956 # - u, eu : uid and effective uid 957 # - l, bl : locale and browser locale 958 # Output: 959 # - return value: URL 960 # 961 # Each element {key val} may optionnally be a single string "key=val", 962 # in which case it must be post-string encoded) 963 # 964 965 proc make-url {_urltab name u eu l bl} { 966 upvar $_urltab urltab 967 968 set path [lindex $urltab($name) 0] 969 set largs [lreplace $urltab($name) 0 0] 970 971 # 972 # Two possible cases: 973 # - URL is a local one (does not begin with "http://") 974 # - URL is external (begins with "http://") 975 # In the last case, don't add default arguments which are 976 # specific to Netmagis application. 977 # 978 979 if {! [regexp {^https?://} $path]} then { 980 # 981 # Add default arguments 982 # 983 984 # user susbtitution 985 if {$u ne $eu} then { 986 lappend largs [list "uid" $u] 987 } 988 989 # default locale 990 if {$l ne $bl} then { 991 lappend largs [list "l" $l] 992 } 993 994 # travel in the application 995 if {$urltab($name:nextprog) ne ""} then { 996 lappend largs [list "nextprog" $urltab($name:nextprog)] 997 lappend largs [list "nextargs" $urltab($name:nextargs)] 998 } 999 1000 # 1001 # Build-up the argument list 1002 # 1003 1004 set l {} 1005 foreach keyval $largs { 1006 if {[llength $keyval] == 1} then { 1007 lappend l $keyval 1008 } else { 1009 lassign $keyval k v 1010 set v [::webapp::post-string $v] 1011 lappend l "$k=$v" 1012 } 1013 } 1014 1015 # 1016 # Build-up URL from path and arguments 1017 # 1018 1019 if {[llength $l] == 0} then { 1020 # no argument: simple case 1021 set url $path 1022 } else { 1023 if {[string match {*\?*} $path]} then { 1024 # already an argument in the path 1025 set url [format "%s&%s" $path [join $l "&"]] 1026 } else { 1027 # not yet an argument in the path 1028 set url [format "%s?%s" $path [join $l "&"]] 1029 } 1030 } 1031 } else { 1032 set url $path 1033 } 1034 1035 unset urltab($name) 1036 return $url 1037 } 1038 1039 # 1040 # Recursive internal method to get links menu 1041 # 1042 # Input: 1043 # - eorm = element (without ":") or module (with ":") 1044 # Output: 1045 # - HTML code for the menu 1046 # 1047 1048 method Get-links {eorm} { 1049 set h "" 1050 if {[info exists links($eorm)]} then { 1051 set lks $links($eorm) 1052 1053 if {[string match ":*" $eorm]} then { 1054 foreach couple $lks { 1055 lassign $couple neorm cond 1056 if {$cond eq "always" || $cond in $curcap} then { 1057 append h [$self Get-links $neorm] 1058 append h "\n" 1059 } 1060 } 1061 } else { 1062 lassign $lks path msg 1063 $self urlset "" $path {} 1064 set url [make-url urltab "" $uid $euid $locale $blocale] 1065 1066 append h [::webapp::helem "li" \ 1067 [::webapp::helem "a" [mc $msg] "href" $url]] 1068 append h "\n" 1069 } 1070 1071 } else { 1072 append h [::webapp::helem "li" [mc "Unknown module '%s'" $eorm] ] 1073 append h "\n" 1074 } 1075 return $h 1076 } 1077 1078 ########################################################################### 1079 # Register a CGI script 1080 # 1081 # Input: 1082 # - crit : criterion list {field regexp field regexp ...} 1083 # - form : form field specification (see webapp::get-data) 1084 # - script : script to execute if criterion matches. 1085 # Variables defined in script: 1086 # - dbfd : database descriptor 1087 # - ftab : field array (see webapp::get-data) 1088 # - tabuid : user's characteristics 1089 # (login, password, lastname, firstname, mail, phone, fax, 1090 # mobile, addr, idcor, idgrp, present) 1091 # Output: (none) 1092 # 1093 1094 method cgi-register {crit form script} { 1095 # 1096 # Memorize field name from criterion 1097 # 1098 foreach {f re} $crit { 1099 lappend critform $f 1100 } 1101 1102 # 1103 # Memorize criterion, form and script 1104 # 1105 lappend critscript [list $crit $form $script] 1106 } 1107 1108 ########################################################################### 1109 # Dispatch to CGI actions 1110 # 1111 # Input: 1112 # - module : current module we are in ("dns", "admin" or "topo") 1113 # or "anon" to access unauthentified pages 1114 # - attr : needed attribute to execute the script 1115 # Output: 1116 # - return value: none 1117 # - object d : Netmagis context 1118 # - object $ah : access to authentication base 1119 # 1120 1121 method cgi-dispatch {module attr} { 1122 global libconf 1123 1124 # 1125 # Builds-up a fictive context to easily return error messages 1126 # 1127 1128 set curmodule "anon" 1129 set curcap {dns} 1130 set locale "C" 1131 set blocale "C" 1132 set scriptmode "cgi" 1133 1134 set debug [get-local-conf "debug"] 1135 1136 # 1137 # Language negociation 1138 # 1139 1140 set blocale [::webapp::locale $avlocale] 1141 $self locale $blocale 1142 1143 # 1144 # Database initialization 1145 # 1146 1147 set msg [init-database $selfns dbfd] 1148 if {$msg ne ""} then { 1149 $self error $msg 1150 } 1151 1152 # 1153 # Keep track of authentication status 1154 # 1155 1156 set authtoken [::webapp::get-cookie "session"] 1157 set authenticated [check-authtoken $dbfd $authtoken login] 1158 1159 if {$attr ne "anon"} then { 1160 # 1161 # Attempt to access a page restricted to valid users 1162 # 1163 1164 if {! $authenticated} then { 1165 1166 # 1167 # Send login page 1168 # 1169 1170 set am [dnsconfig get "authmethod"] 1171 if {$am eq "casldap"} then { 1172 # 1173 # Check for CAS auth first 1174 # 1175 1176 set casurl [dnsconfig get "casurl"] 1177 if {$casurl eq ""} then { 1178 d error [mc "Invalid CAS URL"] 1179 } 1180 set home [::webapp::myurl 1] 1181 if {$home eq ""} then { 1182 d error [mc "Cannot get my own URL"] 1183 } 1184 set url "$casurl/login?service=$home/$libconf(next-login)" 1185 ::webapp::redirect $url 1186 } else { 1187 # 1188 # Normal login page for other authentication methods 1189 # 1190 1191 # For the "logged as" message 1192 set euid "-" 1193 set uid $euid 1194 1195 # 1196 # Send resulting page 1197 # 1198 1199 d urlset "%URLFORM%" $libconf(next-login) {} 1200 d result $libconf(page-login) [list \ 1201 [list %MESSAGE% ""] \ 1202 [list %LOGIN% ""] \ 1203 ] 1204 } 1205 exit 0 1206 } 1207 1208 # 1209 # If we get there, page is restricted to authenticated users, 1210 # and our user is authenticated 1211 # 1212 } 1213 1214 # 1215 # If user is authenticated, check maintenance mode and 1216 # get user attributes 1217 # 1218 1219 if {$authenticated} then { 1220 set uid $login 1221 set euid $login 1222 1223 # 1224 # Maintenance mode : access is forbidden to all, except 1225 # for users specified in ROOT pattern. 1226 # 1227 1228 set ftest [get-local-conf "nologinfile"] 1229 set rootusers [get-local-conf "rootusers"] 1230 if {! [catch [lindex $rootusers 0]]} then { 1231 $self error "Invalid 'rootusers' configuration parameter" 1232 } 1233 1234 if {[file exists $ftest]} then { 1235 if {$uid eq "" || ! ($uid in $rootusers)} then { 1236 set fd [open $ftest "r"] 1237 set msg [read $fd] 1238 close $fd 1239 $self error $msg 1240 } 1241 } 1242 1243 # 1244 # Common initialization work 1245 # 1246 1247 set msg [init-common $selfns $dbfd $login "id" false tabuid] 1248 if {$msg ne ""} then { 1249 $self error $msg 1250 } 1251 1252 set curmodule $module 1253 1254 } else { 1255 set uid "-" 1256 set euid "-" 1257 } 1258 1259 # 1260 # To help write HTML code 1261 # 1262 1263 ::html create ::h 1264 1265 # 1266 # Add default parameters in form analysis 1267 # Default parameters are: 1268 # l : language 1269 # uid : login to be substituted 1270 # nextprog : next action, after current travel 1271 # nextargs : arguments of next action, after current travel 1272 # 1273 1274 lappend form {l 0 1} 1275 lappend form {uid 0 1} 1276 lappend form {nextprog 0 1} 1277 lappend form {nextargs 0 1} 1278 1279 # 1280 # Add dispatch criterions 1281 # 1282 1283 foreach f [lsort -unique $critform] { 1284 lappend form [list $f 0 1] 1285 } 1286 1287 # 1288 # Get variables 1289 # 1290 1291 if {[llength [::webapp::get-data ftab $form]] == 0} then { 1292 set msg [mc "Invalid input"] 1293 if {$debug} then { 1294 append msg "\n$ftab(_error)" 1295 } 1296 $self error $msg 1297 } 1298 1299 # 1300 # Is a specific language required ? 1301 # 1302 1303 set l [string trim [lindex $ftab(l) 0]] 1304 if {$l ne ""} then { 1305 $self locale $l 1306 } 1307 1308 # 1309 # Get next action 1310 # 1311 1312 set dnextprog [string trim [lindex $ftab(nextprog) 0]] 1313 set dnextargs [string trim [lindex $ftab(nextargs) 0]] 1314 1315 # 1316 # Set user capabilities 1317 # 1318 1319 set curcap {anon} 1320 if {$authenticated} then { 1321 # 1322 # Perform user substitution (through the uid parameter) 1323 # 1324 1325 set nuid [string trim [lindex $ftab(uid) 0]] 1326 if {$nuid ne "" && $tabuid(p_admin)} then { 1327 array set tabouid [array get tabuid] 1328 array unset tabuid 1329 1330 set uid $nuid 1331 set login $nuid 1332 1333 set n [read-user $dbfd $login tabuid msg] 1334 if {$n != 1} then { 1335 $self error $msg 1336 } 1337 if {! $tabuid(present)} then { 1338 $self error [mc "User '%s' not authorized" $login] 1339 } 1340 1341 u setlogin $login 1342 } 1343 1344 # 1345 # Computes capabilites, given local installation and/or user rights 1346 # 1347 1348 lappend curcap "dns" 1349 if {[dnsconfig get "topoactive"]} then { 1350 lappend curcap "topo" 1351 } 1352 if {[dnsconfig get "macactive"] && $tabuid(p_mac)} then { 1353 lappend curcap "mac" 1354 } 1355 if {$tabuid(p_genl)} then { 1356 lappend curcap "topogenl" 1357 } 1358 if {$tabuid(p_admin)} then { 1359 lappend curcap "admin" 1360 } 1361 if {[dnsconfig get "authmethod"] eq "pgsql"} then { 1362 lappend curcap "pgauth" 1363 set qlogin [::pgsql::quote $login] 1364 set sql "SELECT r.admin 1365 FROM pgauth.realm r, pgauth.member m 1366 WHERE r.realm = m.realm 1367 AND login = '$qlogin'" 1368 pg_select $dbfd $sql tab { 1369 if {$tab(admin)} then { 1370 lappend curcap "authadmin" 1371 } 1372 } 1373 } 1374 } 1375 1376 # 1377 # Remove additionnal default parameters 1378 # If they were staying in ftab, they could be caught by a 1379 # "hide all ftab paramaters" in a CGI script. 1380 # 1381 1382 foreach p {l uid nextprog nextargs} { 1383 unset ftab($p) 1384 } 1385 1386 1387 # 1388 # Is this page an "admin" only page ? 1389 # 1390 1391 if {[llength $attr] > 0} then { 1392 # XXX : for now, test only one attribute 1393 if {! ($attr in $curcap)} then { 1394 $self error [mc "User '%s' not authorized" $login] 1395 } 1396 } 1397 1398 # 1399 # Find script according to criterion 1400 # 1401 1402 set ok 0 1403 foreach cfs $critscript { 1404 lassign $cfs crit form script 1405 set ok 1 1406 foreach {f re} $crit { 1407 set v [string trim [lindex $ftab($f) 0]] 1408 if {! [regexp "^$re$" $v]} then { 1409 set ok 0 1410 break 1411 } 1412 } 1413 if {$ok} { 1414 break 1415 } 1416 } 1417 1418 if {! $ok} then { 1419 $self error [mc "Cannot find registered CGI action"] 1420 } 1421 1422 # 1423 # Criterion ok 1424 # Get additional form variables and import them into current context 1425 # 1426 1427 if {[llength $form] > 0} then { 1428 if {[llength [::webapp::get-data ftab $form]] == 0} then { 1429 set msg [mc "Invalid input"] 1430 if {$debug} then { 1431 append msg "\n$ftab(_error)" 1432 } 1433 $self error $msg 1434 } 1435 } 1436 1437 # 1438 # Prepare variable import 1439 # 1440 1441 foreach f [lsort -unique $critform] { 1442 lappend form [list $f 0 1] 1443 } 1444 set script "::webapp::import-vars ftab \$form ; $script" 1445 1446 # 1447 # Execute script 1448 # 1449 1450 set r [catch $script msg] 1451 # r=0 (OK), 1 (ERROR), 2 (RETURN), 3 (BREAK) or 4 (CONTINUE) 1452 if {$r == 1} then { 1453 global errorInfo 1454 1455 ::webapp::cgi-err $errorInfo $debug 1456 } 1457 1458 return 0 1459 } 1460 1461 ########################################################################### 1462 # Initialize access to Netmagis, for an autonomous program (command 1463 # line utility, daemon, etc.) 1464 # 1465 # Input: 1466 # - _dbfd : database handle, in return 1467 # - argv0 : script argv0 1468 # - usedefuser : use default user name if login is not found 1469 # - _tabuid : array containing, in return, user's characteristics 1470 # (login, password, lastname, firstname, mail, phone, fax, 1471 # mobile, addr, idcor, idgrp, present) 1472 # Output: 1473 # - return value: error message or empty string 1474 # - object d : Netmagis context 1475 # - object $ah : access to authentication base 1476 # 1477 1478 method init-script {_dbfd argv0 usedefuser _tabuid} { 1479 upvar $_dbfd dbfd 1480 upvar $_tabuid tabuid 1481 1482 set scriptmode "script" 1483 regsub {.*/} $argv0 {} argv0 1484 set scriptargv0 $argv0 1485 1486 # 1487 # Locale 1488 # 1489 1490 uplevel #0 mclocale 1491 uplevel #0 mcload [get-local-conf "msgsdir"] 1492 1493 # 1494 # Look for user's login 1495 # 1496 1497 set cmd [get-local-conf "whoami"] 1498 if {[catch {exec sh -c $cmd} msg]} then { 1499 return "Cannot get login name ($msg)" 1500 } 1501 set login $msg 1502 1503 # 1504 # Database initialization 1505 # 1506 1507 set msg [init-database $selfns dbfd] 1508 if {$msg ne ""} then { 1509 $self error $msg 1510 } 1511 1512 # 1513 # Common initialization work 1514 # 1515 1516 set msg [init-common $selfns $dbfd $login "anon" $usedefuser tabuid] 1517 if {$msg ne ""} then { 1518 return $msg 1519 } 1520 1521 return "" 1522 } 1523 1524 ########################################################################### 1525 # Ends access to Netmagis (CGI script or autonomous program) 1526 # 1527 # Input: 1528 # - none 1529 # Output: 1530 # - return value: none 1531 # 1532 1533 method end {} { 1534 if {$db ne ""} then { 1535 pg_disconnect $db 1536 } 1537 } 1538 1539 1540 method locale {{l {}}} { 1541 set locale "C" 1542 if {$l in $avlocale} then { 1543 set locale $l 1544 } 1545 1546 uplevel #0 mclocale $locale 1547 uplevel #0 mcload [get-local-conf "msgsdir"] 1548 1549 return $locale 1550 } 1551 1552 ########################################################################### 1553 # Returns an error and properly close access to application (and database) 1554 # 1555 # Input: 1556 # - msg : (translated) error message 1557 # Output: 1558 # - return value: none (this method don't return) 1559 # 1560 1561 method error {msg} { 1562 switch $scriptmode { 1563 cgi { 1564 set msg [::webapp::html-string $msg] 1565 regsub -all "\n" $msg "<br>" msg 1566 $self result $errorpage [list [list %MESSAGE% $msg]] 1567 exit 0 1568 } 1569 daemon - 1570 script { 1571 puts stderr "$scriptargv0: $msg" 1572 $self end 1573 exit 1 1574 } 1575 } 1576 } 1577 1578 ########################################################################### 1579 # Returns an error as an image and properly close access to application 1580 # (and database) 1581 # 1582 # Input: 1583 # - msg : (translated) error message 1584 # Output: 1585 # - return value: none (this method don't return) 1586 # 1587 1588 method errimg {msg} { 1589 switch $scriptmode { 1590 cgi { 1591 ::webapp::send png [errimg $msg] 1592 $self end 1593 exit 1 1594 } 1595 daemon - 1596 default { 1597 # should not occur 1598 puts stderr "$scriptargv0: $msg" 1599 $self end 1600 exit 1 1601 } 1602 } 1603 } 1604 1605 ########################################################################### 1606 # Sends a page and properly close access to application (and database) 1607 # 1608 # Input: 1609 # - page : HTML or LaTeX page containing templates 1610 # - lsubst : substitution list for template values 1611 # Output: 1612 # - return value: none 1613 # 1614 1615 method result {page lsubst} { 1616 # 1617 # Define the output format from file extension 1618 # 1619 1620 switch -glob $page { 1621 *.html { set fmt html } 1622 *.tex { set fmt pdf } 1623 default { set fmt "unknown" } 1624 } 1625 1626 # 1627 # Handle internationalized template files 1628 # 1629 1630 set found 0 1631 foreach l [concat [mcpreferences] "C"] { 1632 set tdir [get-local-conf "templatedir"] 1633 set file "$tdir/$l/$page" 1634 if {[file exists $file]} then { 1635 set found 1 1636 break 1637 } 1638 } 1639 if {! $found} then { 1640 error "Template file '$page' not found in locale: [mcpreferences]" 1641 } 1642 1643 # 1644 # Add the "logged as" information 1645 # 1646 1647 set session {} 1648 if {$euid eq "-"} then { 1649 # Not logged in 1650 set m [mc "Log in"] 1651 set url $sessionlinks(login) 1652 append session [::webapp::helem "li" \ 1653 [::webapp::helem "a" "$m" "href" $url] \ 1654 ] 1655 } else { 1656 # Currently logged in 1657 set m [mc "Logged as %s" $euid] 1658 set url $sessionlinks(profile) 1659 append session [::webapp::helem "li" \ 1660 [::webapp::helem "a" "$m" "href" $url] \ 1661 ] 1662 set m [mc "Log out"] 1663 set url $sessionlinks(logout) 1664 append session [::webapp::helem "li" \ 1665 [::webapp::helem "a" "$m" "href" $url] \ 1666 ] 1667 } 1668 lappend lsubst [list %SESSION% $session] 1669 1670 # 1671 # Constitute the links menu if the database access is initialized 1672 # 1673 1674 if {$fmt eq "html"} then { 1675 if {$db eq ""} then { 1676 set linksmenu "" 1677 } else { 1678 set linksmenu [$self Get-links ":$curmodule"] 1679 1680 foreach l $avlocale { 1681 if {$l ne $locale} then { 1682 set utab(L) [list $homepage(:$curmodule)] 1683 set utab(L:nextprog) "" 1684 set url [make-url utab "L" $uid $euid $l $blocale] 1685 append linksmenu [::webapp::helem "li" \ 1686 [::webapp::helem "a" "\[$l\]" "href" $url] \ 1687 ] 1688 } 1689 } 1690 } 1691 1692 lappend lsubst [list %LINKS% $linksmenu] 1693 1694 foreach s [$self urlsubst] { 1695 lappend lsubst $s 1696 } 1697 1698 lappend lsubst [list %VERSION% $version] 1699 } 1700 1701 # 1702 # Path to pdflatex 1703 # 1704 1705 if {$fmt eq "pdf"} then { 1706 set path [get-local-conf "pdflatex"] 1707 if {$path ne ""} then { 1708 ::webapp::cmdpath "pdflatex" $path 1709 } 1710 1711 set pageformat [string tolower [::dnsconfig get "pageformat"]] 1712 switch -- $pageformat { 1713 letter { set pageformat "letterpaper" } 1714 a4 - 1715 default { set pageformat "a4paper" } 1716 } 1717 lappend lsubst [list %PAGEFORMAT% $pageformat] 1718 } 1719 1720 # 1721 # Send resulting page 1722 # 1723 1724 ::webapp::send $fmt [::webapp::file-subst $file $lsubst] 1725 $self end 1726 } 1727 1728 ########################################################################### 1729 # Get the next action (i.e. where we must come back after the current 1730 # travel) 1731 # 1732 # Input: none 1733 # Output: 1734 # - return value: <nextprog> or <nextargs>, depending on method 1735 # 1736 1737 method nextprog {} { 1738 return $dnextprog 1739 } 1740 1741 method nextargs {} { 1742 return $dnextargs 1743 } 1744 1745 ########################################################################### 1746 # Get or set the effective login and idcor of the user 1747 # 1748 # Input: 1749 # - if supplied: list {new effective login, new effective idcor} to set 1750 # (use {- -1} for anonymous user) 1751 # - if not supplied: just get effective login and idcor 1752 # Output: 1753 # - return value: list {login idcor} 1754 # 1755 1756 method euid {{neweuid {}}} { 1757 if {$neweuid ne {}} then { 1758 lassign $neweuid euid eidcor 1759 } 1760 return [list $euid $eidcor] 1761 } 1762 1763 ########################################################################### 1764 # Get or set the real login and idcor of the user 1765 # 1766 # Input: 1767 # - if supplied: real login to set (use "-" for anonymous user) 1768 # - if not supplied: just get login 1769 # Output: 1770 # - return value: login 1771 # 1772 1773 method uid {{newuid {}}} { 1774 if {$newuid ne {}} then { 1775 set uid $newuid 1776 } 1777 return $uid 1778 } 1779 1780 1781 ########################################################################### 1782 # URL framework 1783 # 1784 1785 method urlset {name path {largs {}}} { 1786 set urltab($name) [linsert $largs 0 $path] 1787 set urltab($name:nextprog) "" 1788 } 1789 1790 method urladd {name largs} { 1791 set url($name) [concat $url($name) $largs] 1792 } 1793 1794 method urlsetnext {name nextprog nextargs} { 1795 set urltab($name:nextprog) $nextprog 1796 set urltab($name:nextargs) $nextargs 1797 } 1798 1799 method urladdnext {name} { 1800 if {$dnextprog eq ""} then { 1801 set urltab($name:nextprog) "" 1802 } else { 1803 set urltab($name:nextprog) $dnextprog 1804 set urltab($name:nextargs) $dnextargs 1805 } 1806 } 1807 1808 method urlsubst {} { 1809 set lsubst {} 1810 foreach name [array names urltab] { 1811 if {! [string match "*:*" $name]} then { 1812 set url [$self urlget $name] 1813 lappend lsubst [list $name $url] 1814 } 1815 } 1816 return $lsubst 1817 } 1818 1819 method urlget {name} { 1820 set path [lindex $urltab($name) 0] 1821 set largs [lreplace $urltab($name) 0 0] 1822 set url [make-url urltab $name $uid $euid $locale $blocale] 1823 return $url 1824 } 1825 1826 1827 ########################################################################### 1828 # Sets the context used for the links menu 1829 # 1830 # Input: 1831 # - module : module name (see curmodule and links variables) 1832 # Output: none 1833 # 1834 1835 method module {module} { 1836 set idx ":$module" 1837 if {! [info exists links($idx)]} then { 1838 # This is an internal error 1839 error "'$module' is not a valid module" 1840 } 1841 set curmodule $module 1842 } 1843 1844 ########################################################################### 1845 # Write a line in the log system 1846 # 1847 # Input: 1848 # - event : event name (examples : supprhost, suppralias etc.) 1849 # - message : log message (example: parameters of the event) 1850 # - date (optional) : event date (in seconds since epoch) 1851 # - leuid (optional) : event owner 1852 # - ip (optional) : IP address 1853 # 1854 # Output: none 1855 # 1856 # History : 1857 # 2007/10/?? : jean : design 1858 # 2010/11/09 : pda : dnscontext object and no more login parameter 1859 # 2015/01/14 : pda/jean : add optional parameters date, leuid, ip 1860 # 1861 1862 method writelog {event msg {date {}} {leuid {}} {ip {}}} { 1863 global env 1864 1865 if {$ip eq {}} then { 1866 if {[info exists env(REMOTE_ADDR)]} then { 1867 set ip $env(REMOTE_ADDR) 1868 } else { 1869 set ip "" 1870 } 1871 } 1872 1873 if {$leuid eq {}} then { 1874 set leuid $euid 1875 } 1876 1877 $log log $date $event $leuid $ip $msg 1878 } 1879 1880 # 1881 # Transaction processing 1882 # 1883 1884 method dblock {tablelist} { 1885 set msg "" 1886 if {! [::pgsql::lock $db $tablelist msg]} then { 1887 if {[llength $tablelist] == 0} then { 1888 set tl [join $tablelist ", "] 1889 set msg [mc {Cannot lock table(s) %1$s: %2$s} $tl $msg] 1890 } else { 1891 set msg [mc "Cannot lock database: %s" $msg] 1892 } 1893 if {$scriptmode eq "cgi"} then { 1894 $self error $msg 1895 } 1896 } 1897 return $msg 1898 } 1899 1900 method dbcommit {op} { 1901 set msg "" 1902 if {! [::pgsql::unlock $db "commit" msg]} then { 1903 set msg [$self dbabort $op $msg] 1904 } 1905 return $msg 1906 } 1907 1908 method dbabort {op msg} { 1909 ::pgsql::unlock $db "abort" m 1910 set msg [mc {Cannot perform operation "%1$s": %2$s} $op $msg] 1911 if {$scriptmode eq "cgi"} then { 1912 $self error $msg 1913 } 1914 return $msg 1915 } 1916 1917 method version {} { 1918 return $version 1919 } 1920} 1921 1922::netmagis create d 1923 1924############################################################################## 1925# Configuration parameters 1926############################################################################## 1927 1928# 1929# Configuration parameters class 1930# 1931# This class is a simple way to access to configuration parameters 1932# of the Netmagis application. 1933# 1934# Methods: 1935# - setdb dbfd 1936# set the database handle used to access parameters 1937# - class 1938# returns all known classes 1939# - desc class-or-key 1940# returns the description associated with class or key 1941# - keys [ class ] 1942# returns all keys associed with the class, or all known keys 1943# - keytype key 1944# returns type of a given key, under the format {string|bool|text|menu x} 1945# X is present only for the "menu" type. 1946# - keyhelp key 1947# returns the help message associated with a key 1948# - get key 1949# returns the value associated with a key 1950# - set key val 1951# set the value associated with a key and returns an empty string or 1952# an error message. 1953# 1954# History 1955# 2001/03/21 : pda : design getconfig/setconfig 1956# 2010/10/25 : pda : transform into a class 1957# 2010/12/04 : pda : i18n 1958# 2012/10/27 : pda : add read-only mode 1959# 1960 1961snit::type ::config { 1962 # database handle 1963 variable db "" 1964 1965 # configuration parameter specification 1966 # {{class class-spec} {class class-spec} ...} 1967 # class = class name 1968 # class-spec = {{key ro/rw type} {key ro/rw type} ...} 1969 variable configspec { 1970 {general 1971 {datefmt rw {string}} 1972 {dayfmt rw {string}} 1973 {authmethod rw {menu {{pgsql Internal} {ldap {LDAP}} {casldap CAS}}}} 1974 {authexpire rw {string}} 1975 {authtoklen rw {string}} 1976 {wtmpexpire rw {string}} 1977 {failloginthreshold1 rw {string}} 1978 {faillogindelay1 rw {string}} 1979 {failloginthreshold2 rw {string}} 1980 {faillogindelay2 rw {string}} 1981 {failipthreshold1 rw {string}} 1982 {failipdelay1 rw {string}} 1983 {failipthreshold2 rw {string}} 1984 {failipdelay2 rw {string}} 1985 {pageformat rw {menu {{a4 A4} {letter Letter}}} } 1986 {schemaversion ro {string}} 1987 } 1988 {dns 1989 {defuser rw {string}} 1990 } 1991 {dhcp 1992 {dhcpdefdomain rw {string}} 1993 {dhcpdefdnslist rw {string}} 1994 {default_lease_time rw {string}} 1995 {max_lease_time rw {string}} 1996 {min_lease_time rw {string}} 1997 } 1998 {topo 1999 {topoactive rw {bool}} 2000 {defdomain rw {string}} 2001 {topofrom rw {string}} 2002 {topoto rw {string}} 2003 {topographddelay rw {string}} 2004 {toposendddelay rw {string}} 2005 {topomaxstatus rw {string}} 2006 {sensorexpire rw {string}} 2007 {modeqexpire rw {string}} 2008 {ifchangeexpire rw {string}} 2009 {fullrancidmin rw {string}} 2010 {fullrancidmax rw {string}} 2011 } 2012 {mac 2013 {macactive rw {bool}} 2014 } 2015 {authcas 2016 {casurl rw {string}} 2017 } 2018 {authldap 2019 {ldapurl rw {string}} 2020 {ldapbinddn rw {string}} 2021 {ldapbindpw rw {string}} 2022 {ldapbasedn rw {string}} 2023 {ldapsearchlogin rw {string}} 2024 {ldapattrlogin rw {string}} 2025 {ldapattrname rw {string}} 2026 {ldapattrgivenname rw {string}} 2027 {ldapattrmail rw {string}} 2028 {ldapattrphone rw {string}} 2029 {ldapattrmobile rw {string}} 2030 {ldapattrfax rw {string}} 2031 {ldapattraddr rw {string}} 2032 } 2033 {authpgsql 2034 {authpgminpwlen rw {string}} 2035 {authpgmaxpwlen rw {string}} 2036 {authpgmailfrom rw {string}} 2037 {authpgmailreplyto rw {string}} 2038 {authpgmailcc rw {string}} 2039 {authpgmailbcc rw {string}} 2040 {authpgmailsubject rw {string}} 2041 {authpgmailbody rw {text}} 2042 {authpggroupes rw {string}} 2043 } 2044 } 2045 2046 # 2047 # Internal representation of parameter specification 2048 # 2049 # (class) {<cl1> ... <cln>} 2050 # (class:<cl1>) {<k1> ... <kn>} 2051 # (key:<k1>:type) {string|bool|text|menu ...} 2052 # (key:<k1>:rw) ro|rw 2053 # 2054 2055 variable internal -array {} 2056 2057 constructor {} { 2058 set internal(class) {} 2059 foreach class $configspec { 2060 2061 set classname [lindex $class 0] 2062 lappend internal(class) $classname 2063 set internal(class:$classname) {} 2064 2065 foreach key [lreplace $class 0 0] { 2066 lassign $key keyname keyrw keytype 2067 2068 lappend internal(class:$classname) $keyname 2069 set internal(key:$keyname:type) $keytype 2070 set internal(key:$keyname:rw) $keyrw 2071 } 2072 } 2073 } 2074 2075 method setdb {dbfd} { 2076 set db $dbfd 2077 } 2078 2079 # returns all classes 2080 method class {} { 2081 return $internal(class) 2082 } 2083 2084 # returns textual description of the given class or key 2085 method desc {cork} { 2086 set r $cork 2087 if {[info exists internal(class:$cork)]} then { 2088 set r [mc "cfg:$cork"] 2089 } elseif {[info exists internal(key:$cork:type)]} { 2090 set r [mc "cfg:$cork:desc"] 2091 } 2092 return $r 2093 } 2094 2095 # returns all keys associated with a class (default : all classes) 2096 method keys {{class {}}} { 2097 if {[llength $class] == 0} then { 2098 set class $internal(class) 2099 } 2100 set lk {} 2101 foreach c $class { 2102 set lk [concat $lk $internal(class:$c)] 2103 } 2104 return $lk 2105 } 2106 2107 # returns key rw/ro 2108 method keyrw {key} { 2109 set r "" 2110 if {[info exists internal(key:$key:rw)]} then { 2111 set r $internal(key:$key:rw) 2112 } 2113 return $r 2114 } 2115 2116 # returns key type 2117 method keytype {key} { 2118 set r "" 2119 if {[info exists internal(key:$key:type)]} then { 2120 set r $internal(key:$key:type) 2121 } 2122 return $r 2123 } 2124 2125 # returns key help 2126 method keyhelp {key} { 2127 set r $key 2128 if {[info exists internal(key:$key:type)]} then { 2129 set r [mc "cfg:$key:help"] 2130 } 2131 return $r 2132 } 2133 2134 # returns key value 2135 method get {key} { 2136 if {[info exists internal(key:$key:type)]} then { 2137 set found 0 2138 pg_select $db "SELECT * FROM global.config WHERE key = '$key'" tab { 2139 set val $tab(value) 2140 set found 1 2141 } 2142 if {! $found} then { 2143 switch $internal(key:$key:type) { 2144 string { set val "" } 2145 bool { set val 0 } 2146 set text { set val "" } 2147 set menu { set val "" } 2148 default { set val "type unknown" } 2149 } 2150 } 2151 } else { 2152 error [mc "Unknown configuration key '%s'" $key] 2153 } 2154 return $val 2155 } 2156 2157 # set key value 2158 # returns empty string if ok, or an error message 2159 method set {key val} { 2160 if {[info exists internal(key:$key:rw)]} then { 2161 if {$internal(key:$key:rw) eq "rw"} then { 2162 set r "" 2163 set k [::pgsql::quote $key] 2164 set sql "DELETE FROM global.config WHERE key = '$k'" 2165 if {[::pgsql::execsql $db $sql msg]} then { 2166 set v [::pgsql::quote $val] 2167 set sql "INSERT INTO global.config (key, value) 2168 VALUES ('$k', '$v')" 2169 if {! [::pgsql::execsql $db $sql msg]} then { 2170 set r [mc {Cannot set key '%1$s' to '%2$s': %3$s} $key $val $msg] 2171 } 2172 } else { 2173 set r [mc {Cannot fetch key '%1$s': %2$s} $key $msg] 2174 } 2175 } else { 2176 set r [mc {Cannot modify read-only key '%s'} $key] 2177 } 2178 } else { 2179 error [mc "Unknown configuration key '%s'" $key] 2180 } 2181 2182 return $r 2183 } 2184} 2185 2186############################################################################## 2187# User characteristics 2188############################################################################## 2189 2190# 2191# Netmagis user characteristics class 2192# 2193# This class stores all informations related to current Netmagis user 2194# 2195# Methods: 2196# - setdb dbfd 2197# set the database handle used to access parameters 2198# - setlogin login 2199# set the login name 2200# 2201# .... 2202# 2203# - viewname id 2204# returns view name associated to view id (or empty string if error) 2205# - viewid name 2206# returns view id associated to view name (or -1 if error) 2207# - myviewids 2208# get all authorized view ids 2209# - isallowedview id 2210# check if a view is authorized (1 if ok, 0 if not) 2211# 2212# - domainname id 2213# returns domain name associated to domain id (or empty string if error) 2214# - domainid name 2215# returns domain id associated to domain name (or -1 if error) 2216# - myiddom 2217# get all authorized domain ids 2218# - isalloweddom id 2219# check if a domain is authorized (1 if ok, 0 if not) 2220# 2221# History 2222# 2012/10/31 : pda/jean : design 2223# 2224 2225snit::type ::nmuser { 2226 # database handle 2227 variable db "" 2228 # login of user 2229 variable login "" 2230 2231 # Group management 2232 # Group information is loaded 2233 variable groupsloaded 0 2234 # allgroups(id:<id>)=name 2235 # allgroups(name:<name>)=id 2236 variable allgroups -array {} 2237 2238 # View management 2239 # view information is loaded 2240 variable viewsloaded 0 2241 # allviews(id:<id>)=name 2242 # allviews(name:<name>)=id 2243 variable allviews -array {} 2244 # authviews(<id>)=1 2245 variable authviews -array {} 2246 # myviewids : sorted list of views 2247 variable myviewids {} 2248 2249 # Domain management 2250 # domain information is loaded 2251 variable domainloaded 0 2252 # alldom(id:<id>)=name 2253 # alldom(name:<name>)=id 2254 variable alldom -array {} 2255 # authdom(<id>)=1 2256 variable authdom -array {} 2257 # myiddoms : sorted list of domains 2258 variable myiddom {} 2259 2260 method setdb {dbfd} { 2261 set db $dbfd 2262 } 2263 2264 method setlogin {newlogin} { 2265 if {$login ne $newlogin} then { 2266 set viewsisloaded 0 2267 } 2268 set login $newlogin 2269 } 2270 2271 2272 ####################################################################### 2273 # Group management 2274 ####################################################################### 2275 2276 proc load-groups {selfns} { 2277 array unset allgroups 2278 2279 set sql "SELECT * FROM global.nmgroup" 2280 pg_select $db $sql tab { 2281 set idgrp $tab(idgrp) 2282 set name $tab(name) 2283 set allgroups(id:$idgrp) $name 2284 set allgroups(name:$name) $idgrp 2285 } 2286 set groupsloaded 1 2287 } 2288 2289 method groupname {id} { 2290 if {! $groupsloaded} then { 2291 load-groups $selfns 2292 } 2293 set r -1 2294 if {[info exists allgroups(id:$id)]} then { 2295 set r $allgroups(id:$id) 2296 } 2297 return $r 2298 } 2299 2300 method groupid {name} { 2301 if {! $groupsloaded} then { 2302 load-groups $selfns 2303 } 2304 set r "" 2305 if {[info exists allgroups(name:$name)]} then { 2306 set r $allgroups(name:$name) 2307 } 2308 return $r 2309 } 2310 2311 ####################################################################### 2312 # View management 2313 ####################################################################### 2314 2315 proc load-views {selfns} { 2316 array unset allviews 2317 array unset authviews 2318 set myviewids {} 2319 2320 set sql "SELECT * FROM dns.view" 2321 pg_select $db $sql tab { 2322 set idview $tab(idview) 2323 set name $tab(name) 2324 set allviews(id:$idview) $name 2325 set allviews(name:$name) $idview 2326 } 2327 2328 set qlogin [::pgsql::quote $login] 2329 set sql "SELECT p.idview 2330 FROM dns.p_view p, dns.view v, global.nmuser u 2331 WHERE p.idgrp = u.idgrp 2332 AND p.idview = v.idview 2333 AND u.login = '$qlogin' 2334 ORDER BY p.sort ASC, v.name ASC" 2335 pg_select $db $sql tab { 2336 set idview $tab(idview) 2337 set authviews($idview) 1 2338 lappend myviewids $tab(idview) 2339 } 2340 2341 set viewsloaded 1 2342 } 2343 2344 method viewname {id} { 2345 if {! $viewsloaded} then { 2346 load-views $selfns 2347 } 2348 set r -1 2349 if {[info exists allviews(id:$id)]} then { 2350 set r $allviews(id:$id) 2351 } 2352 return $r 2353 } 2354 2355 method viewid {name} { 2356 if {! $viewsloaded} then { 2357 load-views $selfns 2358 } 2359 set r "" 2360 if {[info exists allviews(name:$name)]} then { 2361 set r $allviews(name:$name) 2362 } 2363 return $r 2364 } 2365 2366 method myviewids {} { 2367 if {! $viewsloaded} then { 2368 load-views $selfns 2369 } 2370 return $myviewids 2371 } 2372 2373 method isallowedview {id} { 2374 if {! $viewsloaded} then { 2375 load-views $selfns 2376 } 2377 return [info exists authviews($id)] 2378 } 2379 2380 ####################################################################### 2381 # Domain management 2382 ####################################################################### 2383 2384 proc load-domains {selfns} { 2385 array unset alldom 2386 array unset authdom 2387 set myiddom {} 2388 2389 set sql "SELECT * FROM dns.domain" 2390 pg_select $db $sql tab { 2391 set iddom $tab(iddom) 2392 set name $tab(name) 2393 set alldom(id:$iddom) $name 2394 set alldom(name:$name) $iddom 2395 } 2396 2397 set qlogin [::pgsql::quote $login] 2398 set sql "SELECT p.iddom 2399 FROM dns.p_dom p, dns.domain d, global.nmuser u 2400 WHERE p.idgrp = u.idgrp 2401 AND p.iddom = d.iddom 2402 AND u.login = '$qlogin' 2403 ORDER BY p.sort ASC, d.name ASC" 2404 pg_select $db $sql tab { 2405 set iddom $tab(iddom) 2406 set authdom($iddom) 1 2407 lappend myiddom $tab(iddom) 2408 } 2409 2410 set domainloaded 1 2411 } 2412 2413 method domainname {id} { 2414 if {! $domainloaded} then { 2415 load-domains $selfns 2416 } 2417 set r -1 2418 if {[info exists alldom(id:$id)]} then { 2419 set r $alldom(id:$id) 2420 } 2421 return $r 2422 } 2423 2424 method domainid {name} { 2425 if {! $domainloaded} then { 2426 load-domains $selfns 2427 } 2428 set r "" 2429 if {[info exists alldom(name:$name)]} then { 2430 set r $alldom(name:$name) 2431 } 2432 return $r 2433 } 2434 2435 method myiddom {} { 2436 if {! $domainloaded} then { 2437 load-domains $selfns 2438 } 2439 return $myiddom 2440 } 2441 2442 method isalloweddom {id} { 2443 if {! $domainloaded} then { 2444 load-domains $selfns 2445 } 2446 return [info exists authdom($id)] 2447 } 2448 2449} 2450 2451############################################################################## 2452# File installation class 2453############################################################################## 2454 2455# 2456# File installation class 2457# 2458# This class is meant to simplify installation of new files in tree 2459# hierarchy. 2460# 2461# When a file is added, its contents are written in a ".new" file and 2462# the name is queued in internal instance variable fileq. 2463# When a commit is requested, all original files are renamed into ".old" 2464# files and ".new" file replace original files. 2465# When an abort is requested, all ".new" files are removed. 2466# 2467# Methods: 2468# - init 2469# reset a new file list 2470# - add filename filecontent 2471# add a new file based on its contents (as a textual value) 2472# returns empty string if succeeds 2473# - abort 2474# reset new files 2475# - commit 2476# apply modifications 2477# returns empty string if succeeds 2478# - uncommit 2479# undo previous commit 2480# returns empty string if succeeds 2481# 2482# History 2483# 2011/06/05 : pda : design 2484# 2485 2486snit::type ::fileinst { 2487 # file queue 2488 variable fileq {} 2489 2490 # state 2491 variable state "init" 2492 2493 # reset queue to empty state 2494 method init {} { 2495 set fileq {} 2496 } 2497 2498 # add a file contents into the queue 2499 method add {name contents} { 2500 if {$state eq "init" || $state eq "nonempty"} then { 2501 set nf "$name.new" 2502 catch {file delete -force $nf} 2503 if {! [catch {set fd [open "$nf" "w"]} err]} then { 2504 puts -nonewline $fd $contents 2505 if {! [catch {close $fd} err]} then { 2506 lappend fileq $name 2507 set err "" 2508 } 2509 } 2510 set state "nonempty" 2511 } else { 2512 set err "cannot add file: state != 'init' && state != 'nonempty'" 2513 } 2514 return $err 2515 } 2516 2517 # commit new files 2518 method commit {} { 2519 set err "" 2520 if {$state eq "init" || $state eq "nonempty"} then { 2521 2522 # we use a "for" loop instead of a "foreach" since the index i 2523 # will be used if anything goes wrong 2524 set n [llength $fileq] 2525 for {set i 0} {$i < $n} {incr i} { 2526 set f [lindex $fileq $i] 2527 set nf "$f.new" 2528 set of "$f.old" 2529 2530 # make a backup of original file if it exists 2531 catch {file delete -force $of} 2532 if {[file exists $f]} then { 2533 if {[catch {file rename -force $f $of} msg]} then { 2534 set err "cannot rename $f to $of\n$msg" 2535 break 2536 } 2537 } 2538 2539 # install new file 2540 if {[catch {file rename $nf $f} msg]} then { 2541 set err "cannot rename $nf to $f\n$msg" 2542 break 2543 } 2544 } 2545 2546 if {$err eq ""} then { 2547 set state "commit" 2548 } else { 2549 for {set j 0} {$j <= $i} {incr j} { 2550 set f [lindex $fileq $j] 2551 set nf "$f.new" 2552 set of "$f.old" 2553 2554 if {! [file exists $nf]} then { 2555 catch {file rename -force $f $nf} 2556 } 2557 2558 if {[file exists $of]} then { 2559 catch {file rename -force $of $f} 2560 } 2561 } 2562 } 2563 } else { 2564 set err "cannot add file: state != 'init' && state != 'nonempty'" 2565 } 2566 2567 return $err 2568 } 2569 2570 # undo previous commit 2571 method uncommit {} { 2572 if {$state eq "commit"} then { 2573 set err "" 2574 set n [llength $fileq] 2575 for {set i 0} {$i < $n} {incr i} { 2576 set f [lindex $fileq $i] 2577 set nf "$f.new" 2578 set of "$f.old" 2579 2580 if {[catch {file rename -force $f $nf} msg]} then { 2581 append err "cannot rename $f to $nf\n$msg\n" 2582 } else { 2583 if {[file exists $of]} then { 2584 if {[catch {file rename -force $of $f} msg]} then { 2585 append err "cannot rename $of to $f\n$msg\n" 2586 } 2587 } 2588 } 2589 } 2590 } else { 2591 set err "cannot commit: state != 'commit'" 2592 } 2593 return $err 2594 } 2595 2596 # abort new files 2597 method abort {} { 2598 foreach f $fileq { 2599 catch {file delete -force "$f.new"} 2600 } 2601 set fileq {} 2602 } 2603} 2604 2605# 2606# Compare old file contents with new contents as a variable 2607# 2608# Input: 2609# - parameters 2610# - file: name of file 2611# - text: new file content 2612# - _errmsg: variable containing error message in return 2613# Output: 2614# - return value: -1 (error), 0 (no change), or 1 (change) 2615# - variable _errmsg: error message, if return value = -1 2616# 2617# History 2618# 2004/03/09 : pda/jean : design 2619# 2011/05/14 : pda : use configuration variables 2620# 2011/05/22 : pda : make it simpler 2621# 2622 2623proc compare-file-with-text {file text _errmsg} { 2624 upvar $_errmsg errmsg 2625 2626 set r 1 2627 if {[file exists $file]} then { 2628 if {[catch {set fd [open $file "r"]} errmsg]} then { 2629 set r -1 2630 } else { 2631 set old [read $fd] 2632 close $fd 2633 2634 if {$old eq $text} then { 2635 set r 0 2636 } 2637 } 2638 } 2639 2640 return $r 2641} 2642 2643# 2644# Show difference between old file and new contents 2645# 2646# Input: 2647# - parameters 2648# - fd : file descriptor 2649# - cmd: diff command 2650# - file: name of file 2651# - text: new file content 2652# - _errmsg: variable containing error message in return 2653# Output: 2654# - return value: 1 (ok) or 0 (error) 2655# - variable _errmsg: error message, if return value = 0 2656# 2657# History 2658# 2011/05/22 : pda : specification 2659# 2011/06/10 : pda : add fd parameter 2660# 2011/06/10 : pda : add special case for non-existant file 2661# 2662 2663proc show-diff-file-text {fd cmd file text} { 2664 if {! [file exists $file]} then { 2665 set file "/dev/null" 2666 } 2667 set c [format $cmd $file] 2668 append c "|| exit 0" 2669 catch {exec sh -c $c << $text} r 2670 puts $fd $r 2671} 2672 2673############################################################################## 2674# Graphviz graphs 2675############################################################################## 2676 2677# 2678# Graph generation class with Graphviz 2679# 2680# This class is a simple way to generate a Netmagis graph. 2681# 2682# Methods: 2683# - reset 2684# reset graph parameters 2685# set the output format for the graph 2686# - title <string> 2687# set graph title (default: empty string, hence no title) 2688# - node <nodename> { <attr> ... } (with <attr> ::= "name=value") 2689# set a node 2690# - link <nodename> <nodename> { <attr> ... } 2691# mark a link between nodes 2692# - graphviz <png|pdf> <engine> <dot path> <ps2pdf path> 2693# calls graphviz on the current graph and returns 1 if success 2694# and 0 if error. 2695# - error 2696# returns error message from graphviz call (if graphviz method returned 0) 2697# - output 2698# returns generated graph (if graphviz method returned 1) 2699# 2700# History 2701# 2011/12/29 : pda : design 2702# 2012/01/18 : pda : only one dot command for all layout engines 2703# 2704 2705snit::type ::gvgraph { 2706 2707 variable title "" 2708 variable nnodes 0 2709 variable nodesandlinks {} 2710 variable error "" 2711 variable output "" 2712 2713 # graph skeleton 2714 # %1$s : nodes and links 2715 # %2$s : graph title 2716 # %3$s : layout engine (dot or neato) 2717 # %4$s : page & size attributes (meaningful only for PDF graphs) 2718 variable skeleton -array { 2719 map { 2720 graph g { 2721 layout = %3$s; 2722 charset = "UTF-8"; 2723 fontsize = 14; 2724 fontname = Helvetica; 2725 margin = .3; 2726 center = true; 2727 orientation = portrait; 2728 maxiter = 1000 ; 2729 node [fontname=Helvetica,fontsize=10, color=grey]; 2730 edge [fontname=Helvetica,fontsize=8, len=1.4, labelfontname=Helvetica, labelfontsize=6, color=grey]; 2731 overlap = false; 2732 spline = true; 2733 %1$s 2734 %2$s 2735 } 2736 } 2737 png { 2738 graph g { 2739 layout = %3$s; 2740 charset = "UTF-8"; 2741 fontsize = 14; 2742 fontname = Helvetica; 2743 margin = .3; 2744 center = true; 2745 orientation = portrait; 2746 maxiter = 1000 ; 2747 node [fontname=Helvetica,fontsize=10, color=grey]; 2748 edge [fontname=Helvetica,fontsize=8, len=1.4, labelfontname=Helvetica, labelfontsize=6, color=grey]; 2749 overlap = false; 2750 spline = true; 2751 %1$s 2752 %2$s 2753 } 2754 } 2755 pdf { 2756 graph g { 2757 layout = %3$s; 2758 charset = "UTF-8"; 2759 fontsize = 14; 2760 fontname = Helvetica; 2761 margin = .3; 2762 center = true; 2763 %4$s 2764 orientation = landscape; 2765 maxiter = 1000 ; 2766 node [fontname=Helvetica,fontsize=10, color=grey]; 2767 edge [fontname=Helvetica,fontsize=8, len=1.4, labelfontname=Helvetica, labelfontsize=6, color=grey]; 2768 overlap = false; 2769 spline = true; 2770 %1$s 2771 %2$s 2772 } 2773 } 2774 } 2775 2776 # %1$s : path to the dot cmd 2777 # %2$s : path to the ps2pdf cmd 2778 # %3$s : dot file name 2779 # %4$s : error file name 2780 variable gvcmd -array { 2781 map {|%1$s -Tcmapx %3$s 2>%4$s} 2782 png {|%1$s -Tpng %3$s 2>%4$s} 2783 pdf {|%1$s -Tps %3$s 2>%4$s | %2$s - -} 2784 } 2785 2786 # reset graph to initial state 2787 method reset {} { 2788 set title "" 2789 set nodesandlinks {} 2790 set nnodes 0 2791 set error "" 2792 set output "" 2793 } 2794 2795 # returns an error message if format is not valid 2796 method check-format {format} { 2797 if {! [info exists skeleton($format)]} then { 2798 return [format [mc "Invalid format '%s'"] $format] 2799 } 2800 return "" 2801 2802 } 2803 2804 # set title of the graph (empty string means no title) 2805 method title {t} { 2806 set title $t 2807 } 2808 2809 # add a node to the graph 2810 method node {name attrlist} { 2811 set attr [join $attrlist ","] 2812 lappend nodesandlinks "\"$name\" \[$attr\];" 2813 } 2814 2815 # add a link to the graph 2816 method link {n1 n2 attrlist} { 2817 set attr [join $attrlist ","] 2818 lappend nodesandlinks "\"$n1\" -- \"$n2\" \[$attr\];" 2819 } 2820 2821 # calls graphviz and returns 1 if no error. Caller must use 2822 # error and output methods to get the result. 2823 method graphviz {format engine dotcmd ps2pdfcmd} { 2824 # 2825 # Barks if format is invalid 2826 # 2827 set error [$self check-format $format] 2828 if {$error ne ""} then { 2829 return 0 2830 } 2831 2832 # temporary file 2833 set tmp "/tmp/gv-[pid]" 2834 2835 # 2836 # Builds the gv (dot) file for the graph 2837 # 2838 2839 # title 2840 if {$title eq ""} then { 2841 set t "" 2842 } else { 2843 set t "label = \"$title\";\n" 2844 } 2845 2846 # page format 2847 set pageformat [string tolower [::dnsconfig get "pageformat"]] 2848 switch -- $pageformat { 2849 letter { set paper {page = "8.5,11"; size = "10.3,7.8";} } 2850 a4 - 2851 default { set paper {page = "8.26,11.69"; size = "11,7.6";} } 2852 } 2853 2854 set dot [format $skeleton($format) \ 2855 [join $nodesandlinks "\n"] \ 2856 $t \ 2857 $engine \ 2858 $paper 2859 ] 2860 2861 set fd [open "$tmp.gv" "w"] 2862 fconfigure $fd -encoding utf-8 2863 puts $fd $dot 2864 close $fd 2865 2866 # 2867 # Calls graphviz 2868 # 2869 2870 set cmd [format $gvcmd($format) $dotcmd $ps2pdfcmd $tmp.gv $tmp.err] 2871 2872 if {[catch {open $cmd "r"} fd]} then { 2873 set error [format [mc "Error generating graph: %s"] $fd] 2874 set r 0 2875 } else { 2876 fconfigure $fd -translation binary 2877 set output [read $fd] 2878 if {[catch {close $fd} error]} then { 2879 set r 0 2880 } else { 2881 set r 1 2882 } 2883 } 2884 2885 # 2886 # Has an error occurred? 2887 # 2888 2889 if {$r == 0} then { 2890 if {! [catch {open $tmp.err "r"} fderr]} then { 2891 append error "\n" 2892 append error [read $fderr] 2893 close $fderr 2894 } 2895 } 2896 2897 file delete -force -- $tmp.gv $tmp.err 2898 2899 # 2900 # Returns appropriate code : 1 (success) or 0 (failure) 2901 # 2902 2903 return $r 2904 } 2905 2906 # returns the error message resulting from the previous graphviz invocation 2907 method error {} { 2908 return $error 2909 } 2910 2911 # returns the output resulting from the previous graphviz invocation 2912 method output {} { 2913 return $output 2914 } 2915} 2916 2917############################################################################## 2918# Generates an error message as a bitmap image 2919############################################################################## 2920 2921proc errimg {msg} { 2922 set gv [::gvgraph %AUTO%] 2923 $gv node "ERROR $msg" {shape=rectangle color=red style=filled} 2924 if {[$gv graphviz "png" "dot" [get-local-conf "dot"] ""]} then { 2925 set img [$gv output] 2926 } else { 2927 # ouch! This is a text... 2928 set img [$gv error] 2929 } 2930 $gv destroy 2931 return $img 2932} 2933 2934############################################################################## 2935# Get graphviz node attributes from a regular expression 2936############################################################################## 2937 2938# 2939# Initialize data structure for dotattr-match-get 2940# 2941# Input: 2942# - parameters: 2943# - dbfd : database handle 2944# - type : 2 or 3, depending upon the type of graph 2945# - _tabdot : empty data structure for pattern matching 2946# Output: 2947# - return value: none 2948# - parameter _tabdot: 2949# tabdot(_) {<re> ... <re>} (in matching order) 2950# tabdot(<re>) <attributes> 2951# 2952# History 2953# 2012/01/09 : pda : design 2954# 2955 2956proc dotattr-match-init {dbfd type _tabdot} { 2957 upvar $_tabdot tabdot 2958 2959 catch {unset tabdot} 2960 set sql "SELECT regexp, gvattr FROM topo.dotattr 2961 WHERE type = $type ORDER BY rank" 2962 set tabdot(_) {} 2963 pg_select $dbfd $sql tab { 2964 set re $tab(regexp) 2965 set at $tab(gvattr) 2966 lappend tabdot(_) $re 2967 set tabdot($re) $at 2968 } 2969} 2970 2971# 2972# Match a string against regexp in order to find graphviz node attributes 2973# 2974# Input: 2975# - parameters: 2976# - string : string to match (x/y for L2 graph, x for L3 graph) 2977# - _tabdot : array initialized by dotattr-match-init 2978# Output: 2979# - return value: graphviz attributes 2980# 2981# History 2982# 2012/01/09 : pda : design 2983# 2984 2985proc dotattr-match-get {str _tabdot} { 2986 upvar $_tabdot tabdot 2987 2988 set attr {} 2989 foreach re $tabdot(_) { 2990 if {[regexp $re $str]} then { 2991 set attr $tabdot($re) 2992 break 2993 } 2994 } 2995 return $attr 2996} 2997 2998 2999############################################################################## 3000# HTML mask/unmask class 3001############################################################################## 3002 3003# 3004# HTML class 3005# 3006# This class provides methods to simplify HTML writing 3007# 3008# Methods: 3009# - reset 3010# reset HTML parameters 3011# - mask-next 3012# increment mask counter 3013# - mask-link <text> 3014# HTML code for the link to unmask/mask text 3015# - mask-text <text> 3016# HTML code to mask the text (such as it may be unmasked by the link) 3017# 3018# Note: this class needs an "invdisp" Javascript function in the 3019# HTML page 3020# 3021# History 3022# 2012/12/19 : pda/jean : design 3023# 3024 3025snit::type ::html { 3026 3027 variable mask_counter 0 3028 3029 # reset to initial state 3030 method reset {} { 3031 set mask_counter 0 3032 } 3033 3034 # increment mask counter 3035 method mask-next {} { 3036 incr mask_counter 3037 } 3038 3039 # HTML code for the link to unmask/mask text 3040 method mask-link {text} { 3041 return [::webapp::helem "a" $text \ 3042 "href" "#" \ 3043 "onclick" "invdisp('hv$mask_counter')" \ 3044 ] 3045 } 3046 3047 # HTML code to mask the text (such as it may be unmasked by the link) 3048 method mask-text {text} { 3049 return [::webapp::helem "div" $text \ 3050 "id" "hv$mask_counter" \ 3051 "style" "display:none" \ 3052 ] 3053 } 3054} 3055 3056############################################################################## 3057# Cosmetic 3058############################################################################## 3059 3060# 3061# Format a string such as it correctly displays in an array 3062# 3063# Input: 3064# - parameters: 3065# - string : string to display 3066# Output: 3067# - return value: same string, with " " if empty 3068# 3069# History 3070# 2002/05/23 : pda : design 3071# 2010/11/29 : pda : i18n 3072# 3073 3074proc html-tab-string {string} { 3075 set v [::webapp::html-string $string] 3076 if {[string trim $v] eq ""} then { 3077 set v " " 3078 } 3079 return $v 3080} 3081 3082# 3083# Display user data in an HTML array 3084# 3085# Input: 3086# - parameters: 3087# - tabuid : array containing user's attributes 3088# - global variables : 3089# - libconf(tabuser) : array specification 3090# Output: 3091# - return value: HTML code ready to use 3092# 3093# History 3094# 2002/07/25 : pda : design 3095# 2003/05/13 : pda/jean : use tabuid 3096# 2010/11/29 : pda : i18n 3097# 3098 3099proc display-user {_tabuid} { 3100 global libconf 3101 upvar $_tabuid tabuid 3102 3103 set lines {} 3104 lappend lines [list Normal [mc "User"] "$tabuid(lastname) $tabuid(firstname)"] 3105 foreach {txt key} { 3106 Login login 3107 Mail mail 3108 Phone phone 3109 Mobile mobile 3110 Fax fax 3111 Address addr 3112 } { 3113 lappend lines [list Normal [mc $txt] $tabuid($key)] 3114 } 3115 return [::arrgen::output "html" $libconf(tabuser) $lines] 3116} 3117 3118# 3119# Display last connections from one user or from all users 3120# 3121# Input: 3122# - parameters: 3123# - dbfd : database handle 3124# - idcor: id of user, or -1 for all users 3125# Output: 3126# - return value: HTML code ready to use 3127# 3128# History 3129# 2015/06/05 : pda/jean : design 3130# 3131 3132proc display-last-connections {dbfd idcor} { 3133 global libconf 3134 3135 if {$idcor == -1} then { 3136 set where "" 3137 set limit "" 3138 } else { 3139 set where "AND w.idcor = $idcor" 3140 set limit "LIMIT 10" 3141 } 3142 3143 set sql "SELECT u.login, w.start, w.ip, w.stop, w.stopreason 3144 FROM global.wtmp w, global.nmuser u 3145 WHERE w.idcor = u.idcor $where 3146 ORDER BY start DESC 3147 $limit" 3148 set lines {} 3149 lappend lines [list "Title" [mc "Login"] \ 3150 [mc "Login time"] \ 3151 [mc "IP address"] \ 3152 [mc "Logout time"] \ 3153 [mc "Logout reason"] \ 3154 ] 3155 pg_select $dbfd $sql tab { 3156 lappend lines [list "Normal" \ 3157 $tab(login) \ 3158 $tab(start) \ 3159 $tab(ip) \ 3160 $tab(stop) \ 3161 $tab(stopreason) \ 3162 ] 3163 } 3164 return [::arrgen::output "html" $libconf(tabwtmp) $lines] 3165} 3166 3167# 3168# Display group data in an HTML array 3169# 3170# Input: 3171# - parameters: 3172# - dbfd : database handle 3173# - idgrp : group id 3174# - global variables libconf(tab*) : array specification 3175# Output: 3176# - return value: list of 9 HTML strings 3177# 3178# History 3179# 2002/05/23 : pda/jean : specification et design 3180# 2005/04/06 : pda : add DHCP profiles 3181# 2007/10/23 : pda/jean : add users 3182# 2008/07/23 : pda/jean : add group permissions 3183# 2010/10/31 : pda : add ttl permission 3184# 2010/11/03 : pda/jean : add equipment permissions 3185# 2010/11/30 : pda/jean : add mac permissions 3186# 2010/12/01 : pda : i18n 3187# 2012/01/21 : jean : add generate link number permissions 3188# 2012/10/08 : pda/jean : add views 3189# 3190 3191proc display-group {dbfd idgrp} { 3192 global libconf 3193 3194 # 3195 # Get specific permissions: p_admin, p_smtp, p_ttl, p_mac and p_genl 3196 # 3197 3198 set lines {} 3199 set sql "SELECT p_admin, p_smtp, p_ttl, p_mac, p_genl 3200 FROM global.nmgroup 3201 WHERE idgrp = $idgrp" 3202 pg_select $dbfd $sql tab { 3203 if {$tab(p_admin)} then { 3204 set p_admin [mc "yes"] 3205 } else { 3206 set p_admin [mc "no"] 3207 } 3208 if {$tab(p_smtp)} then { 3209 set p_smtp [mc "yes"] 3210 } else { 3211 set p_smtp [mc "no"] 3212 } 3213 if {$tab(p_ttl)} then { 3214 set p_ttl [mc "yes"] 3215 } else { 3216 set p_ttl [mc "no"] 3217 } 3218 if {$tab(p_mac)} then { 3219 set p_mac [mc "yes"] 3220 } else { 3221 set p_mac [mc "no"] 3222 } 3223 if {$tab(p_genl)} then { 3224 set p_genl [mc "yes"] 3225 } else { 3226 set p_genl [mc "no"] 3227 } 3228 lappend lines [list Normal [mc "Netmagis administration"] $p_admin] 3229 lappend lines [list Normal [mc "SMTP authorization management"] $p_smtp] 3230 lappend lines [list Normal [mc "TTL management"] $p_ttl] 3231 lappend lines [list Normal [mc "MAC module access"] $p_mac] 3232 lappend lines [list Normal [mc "Generate link numbers"] $p_genl] 3233 } 3234 if {[llength $lines] > 0} then { 3235 set tabperm [::arrgen::output "html" $libconf(tabperm) $lines] 3236 } else { 3237 set tabperm [mc "Error on group permissions"] 3238 } 3239 3240 # 3241 # Get the list of users in this group 3242 # 3243 3244 set luser {} 3245 set sql "SELECT login FROM global.nmuser WHERE idgrp=$idgrp ORDER BY login" 3246 pg_select $dbfd $sql tab { 3247 lappend luser [::webapp::html-string $tab(login)] 3248 } 3249 set tabuser [join $luser ", "] 3250 3251 # 3252 # Get IP ranges allowed to the group 3253 # 3254 3255 set lines {} 3256 set sql "SELECT n.idnet, 3257 n.name, n.location, n.addr4, n.addr6, 3258 p.dhcp, p.acl, 3259 o.name AS org, 3260 c.name AS comm 3261 FROM dns.network n, dns.p_network p, 3262 dns.organization o, dns.community c 3263 WHERE p.idgrp = $idgrp 3264 AND p.idnet = n.idnet 3265 AND o.idorg = n.idorg 3266 AND c.idcomm = n.idcomm 3267 ORDER BY p.sort, n.addr4, n.addr6" 3268 pg_select $dbfd $sql tab { 3269 set n_name [::webapp::html-string $tab(name)] 3270 set n_loc [::webapp::html-string $tab(location)] 3271 set n_org $tab(org) 3272 set n_comm $tab(comm) 3273 set n_dhcp $tab(dhcp) 3274 set n_acl $tab(acl) 3275 3276 # dispaddr : used for a pleasant address formatting 3277 set dispaddr {} 3278 # where : part of the WHERE clause for address selection 3279 set where {} 3280 foreach a {addr4 addr6} { 3281 if {$tab($a) ne ""} then { 3282 lappend dispaddr $tab($a) 3283 lappend where "addr <<= '$tab($a)'" 3284 } 3285 } 3286 set dispaddr [join $dispaddr ", "] 3287 set where [join $where " OR "] 3288 3289 lappend lines [list Network $n_name] 3290 lappend lines [list Normal4 [mc "Location"] $n_loc \ 3291 [mc "Organization"] $n_org] 3292 lappend lines [list Normal4 [mc "Range"] $dispaddr \ 3293 [mc "Community"] $n_comm] 3294 3295 set perm {} 3296 3297 set pnet {} 3298 if {$n_dhcp} then { lappend pnet "dhcp" } 3299 if {$n_acl} then { lappend pnet "acl" } 3300 if {[llength $pnet] > 0} then { 3301 lappend perm [join $pnet ", "] 3302 } 3303 set sql2 "SELECT addr, allow_deny 3304 FROM dns.p_ip 3305 WHERE ($where) 3306 AND idgrp = $idgrp 3307 ORDER BY addr" 3308 pg_select $dbfd $sql2 tab2 { 3309 if {$tab2(allow_deny)} then { 3310 set x "+" 3311 } else { 3312 set x "-" 3313 } 3314 lappend perm "$x $tab2(addr)" 3315 } 3316 3317 lappend lines [list Perm [mc "Permissions"] [join $perm "\n"]] 3318 } 3319 3320 if {[llength $lines] > 0} then { 3321 set tabnetworks [::arrgen::output "html" $libconf(tabnetworks) $lines] 3322 } else { 3323 set tabnetworks [mc "No allowed network"] 3324 } 3325 3326 # 3327 # Get IP permissions out of network ranges identified above. 3328 # 3329 3330 set lines {} 3331 set found 0 3332 set sql "SELECT addr, allow_deny 3333 FROM dns.p_ip 3334 WHERE NOT (addr <<= ANY ( 3335 SELECT n.addr4 3336 FROM dns.network n, dns.p_network p 3337 WHERE n.idnet = p.idnet 3338 AND n.addr4 IS NOT NULL 3339 AND p.idgrp = $idgrp 3340 UNION 3341 SELECT n.addr6 3342 FROM dns.network n, dns.p_network p 3343 WHERE n.idnet = p.idnet 3344 AND n.addr6 IS NOT NULL 3345 AND p.idgrp = $idgrp 3346 ) ) 3347 AND idgrp = $idgrp 3348 ORDER BY addr" 3349 set perm {} 3350 pg_select $dbfd $sql tab { 3351 set found 1 3352 if {$tab(allow_deny)} then { 3353 set x "+" 3354 } else { 3355 set x "-" 3356 } 3357 lappend perm "$x $tab(addr)" 3358 } 3359 lappend lines [list Perm [mc "Permissions"] [join $perm "\n"]] 3360 3361 if {$found} then { 3362 set tabcidralone [::arrgen::output "html" $libconf(tabnetworks) $lines] 3363 } else { 3364 set tabcidralone [mc "None (it's ok)"] 3365 } 3366 3367 # 3368 # Get views 3369 # 3370 3371 set lines {} 3372 lappend lines [list Title [mc "View"] [mc "Selected by default"]] 3373 set sql "SELECT view.name AS name, p_view.selected 3374 FROM dns.p_view, dns.view 3375 WHERE p_view.idview = view.idview 3376 AND p_view.idgrp = $idgrp 3377 ORDER BY p_view.sort ASC, view.name ASC" 3378 pg_select $dbfd $sql tab { 3379 set sel "" 3380 if {$tab(selected)} then { 3381 set sel [mc "Yes"] 3382 } else { 3383 set sel [mc "No"] 3384 } 3385 3386 lappend lines [list Normal $tab(name) $sel] 3387 } 3388 if {[llength $lines] > 0} then { 3389 set tabviews [::arrgen::output "html" $libconf(tabviews) $lines] 3390 } else { 3391 set tabviews [mc "No allowed view"] 3392 } 3393 3394 # 3395 # Get domains 3396 # 3397 3398 set lines {} 3399 lappend lines [list Title [mc "Domain"] [mc "Mail role management"]] 3400 set sql "SELECT domain.name AS name, p_dom.mailrole 3401 FROM dns.p_dom, dns.domain 3402 WHERE p_dom.iddom = domain.iddom 3403 AND p_dom.idgrp = $idgrp 3404 ORDER BY p_dom.sort, domain.name" 3405 pg_select $dbfd $sql tab { 3406 set rm "" 3407 if {$tab(mailrole)} then { 3408 set rm [mc "Yes"] 3409 } else { 3410 set rm [mc "No"] 3411 } 3412 lappend lines [list Normal $tab(name) $rm] 3413 } 3414 if {[llength $lines] > 0} then { 3415 set tabdomains [::arrgen::output "html" $libconf(tabdomains) $lines] 3416 } else { 3417 set tabdomains [mc "No allowed domain"] 3418 } 3419 3420 # 3421 # Get DHCP profiles 3422 # 3423 3424 set lines {} 3425 set sql "SELECT d.name, p.sort, d.text 3426 FROM dns.dhcpprofile d, dns.p_dhcpprofile p 3427 WHERE d.iddhcpprof = p.iddhcpprof 3428 AND p.idgrp = $idgrp 3429 ORDER BY p.sort, d.name" 3430 pg_select $dbfd $sql tab { 3431 lappend lines [list DHCP $tab(name) $tab(text)] 3432 } 3433 if {[llength $lines] > 0} then { 3434 set tabdhcpprofile [::arrgen::output "html" $libconf(tabdhcpprofile) $lines] 3435 } else { 3436 set tabdhcpprofile [mc "No allowed DHCP profile"] 3437 } 3438 3439 # 3440 # Get equipment permissions 3441 # 3442 3443 set lines {} 3444 foreach {rw text} [list 0 [mc "Read"] 1 [mc "Write"]] { 3445 set sql "SELECT allow_deny, pattern 3446 FROM topo.p_eq 3447 WHERE idgrp = $idgrp AND rw = $rw 3448 ORDER BY rw, allow_deny DESC, pattern" 3449 set perm "" 3450 pg_select $dbfd $sql tab { 3451 if {$tab(allow_deny) eq "0"} then { 3452 set allow_deny "-" 3453 } else { 3454 set allow_deny "+" 3455 } 3456 append perm "$allow_deny $tab(pattern)\n" 3457 } 3458 if {$perm eq ""} then { 3459 set perm [mc "No permission"] 3460 } 3461 lappend lines [list PermEq $text $perm] 3462 } 3463 set tabdreq [::arrgen::output "html" $libconf(tabdreq) $lines] 3464 3465 # 3466 # Get VLAN-ids for L2-only networks 3467 # 3468 3469 set lines {} 3470 set sql "SELECT p.vlanid, v.descr 3471 FROM topo.p_l2only p, topo.vlan v 3472 WHERE p.idgrp = $idgrp AND v.vlanid = p.vlanid 3473 ORDER BY p.vlanid ASC" 3474 pg_select $dbfd $sql tab { 3475 lappend lines [list Normal "$tab(vlanid) - $tab(descr)"] 3476 } 3477 if {[llength $lines] > 0} then { 3478 set tabl2only [::arrgen::output "html" $libconf(tabl2only) $lines] 3479 } else { 3480 set tabl2only [mc "No allowed L2-only network"] 3481 } 3482 3483 # 3484 # Return informations 3485 # 3486 3487 return [list $tabperm \ 3488 $tabuser \ 3489 $tabnetworks \ 3490 $tabcidralone \ 3491 $tabviews \ 3492 $tabdomains \ 3493 $tabdhcpprofile \ 3494 $tabdreq \ 3495 $tabl2only \ 3496 ] 3497} 3498 3499############################################################################## 3500# Cryptographic functions 3501############################################################################## 3502 3503# 3504# Crypt a password 3505# 3506# Input: 3507# - parameters : 3508# - str : string to crypt 3509# Output: 3510# - return value : crypted string 3511# 3512# History 3513# 2003/05/13 : pda/jean : design 3514# 2005/07/22 : pda/jean : secure special characters 3515# 2010/12/29 : pda : i18n and netmagis merge 3516# 2013/02/08 : pda/jean : apply schplurtz's patch 3517# 2014/05/09 : pda/jean : use md5crypt tcllib package 3518# 3519 3520proc pgauth-crypt {str} { 3521 return [md5crypt::md5crypt $str [::md5crypt::salt]] 3522} 3523 3524# 3525# Check a user-provided clear-text password against the crypted one in database 3526# 3527# Input: 3528# - parameters : 3529# - pw : clear-text password provided by the user 3530# - refpw : encrypted password from the database 3531# Output: 3532# - return value : true if the encrypted passwords match 3533# 3534# History 3535# 2014/05/09 : pda/jean : design 3536# 3537 3538proc pgauth-checkpw {pw pwref} { 3539 set success false 3540 if {[regexp {^\$1\$([^\$]+)\$} $pwref dummy salt]} then { 3541 set crypted [::md5crypt::md5crypt $pw $salt] 3542 if {$crypted eq $pwref} then { 3543 set success true 3544 } 3545 } 3546 return $success 3547} 3548 3549# 3550# Generate a semi-random password 3551# 3552# Input: 3553# - parameters : (none) 3554# Output: 3555# - return value : generated clear-text password 3556# 3557# History 3558# 2003/06/13 : pda/jean : design 3559# 2010/12/29 : pda : i18n and netmagis merge 3560# 3561 3562proc pgauth-genpw {} { 3563 set pwgen [get-local-conf "pwgen"] 3564 return [exec sh -c $pwgen] 3565} 3566 3567# 3568# Generate n bytes of random 3569# 3570# Input: 3571# - parameters : 3572# - nbytes: number of bytes 3573# Output: 3574# - return value : random string of hex characters 3575# 3576# History 3577# 2014/05/09 : pda/jean : design 3578# 3579 3580proc get-random {nbytes} { 3581 set dev [get-local-conf "random"] 3582 if {[catch {set fd [open $dev {RDONLY BINARY}]} msg]} then { 3583 # 3584 # Silently fall-back to a non cryptographically secure random 3585 # if /dev/random is not available 3586 # 3587 expr srand([clock clicks -microseconds]) 3588 set r "" 3589 for {set i 0} {$i < $nbytes} {incr i} { 3590 append r [binary format "c" [expr int(rand()*256)]] 3591 } 3592 } else { 3593 # 3594 # Successful open: read random bytes 3595 # 3596 set r [read $fd $nbytes] 3597 close $fd 3598 } 3599 3600 binary scan $r "H*" hex 3601 return $hex 3602} 3603 3604############################################################################## 3605# Authentication 3606############################################################################## 3607 3608# 3609# Check authentication token 3610# 3611# Input: 3612# - parameters: 3613# - dbfd : database handle 3614# - token : authentication token (given by the session cookie) 3615# - _login : in return, login of user or "" if the token is not valid 3616# Output: 3617# - return value: true if the token is a valid authentication token 3618# 3619# History 3620# 2014/04/11 : pda/jean : design 3621# 2015/02/04 : pda/jean : simplify session management with *tmp tables 3622# 3623 3624proc check-authtoken {dbfd token _login} { 3625 upvar $_login login 3626 3627 set idle [dnsconfig get "authexpire"] 3628 set wtmpexpire [dnsconfig get "wtmpexpire"] 3629 3630 # 3631 # Expire old utmp entries 3632 # 3633 3634 d dblock {global.utmp global.wtmp} 3635 3636 # Get the list of expired sessions for the log (see below) 3637 3638 set sql "SELECT u.login, t.token, t.lastaccess 3639 FROM global.nmuser u, global.utmp t 3640 WHERE t.lastaccess < NOW() - interval '$idle second' 3641 AND u.idcor = t.idcor" 3642 set lexp {} 3643 pg_select $dbfd $sql tab { 3644 lappend lexp [list $tab(login) $tab(token) $tab(lastaccess)] 3645 } 3646 3647 # Transfer all expired utmp entries to wtmp 3648 # and delete old wtmp entries 3649 3650 set sql "INSERT INTO global.wtmp (idcor, token, start, ip, stop, stopreason) 3651 SELECT idcor, token, start, ip, lastaccess, 'expired' 3652 FROM global.utmp 3653 WHERE lastaccess < NOW() - interval '$idle second' 3654 ; 3655 DELETE FROM global.utmp 3656 WHERE lastaccess < NOW() - interval '$idle second' 3657 ; 3658 DELETE FROM global.wtmp 3659 WHERE stop < NOW() - interval '$wtmpexpire day' 3660 " 3661 if {! [::pgsql::execsql $dbfd $sql msg]} then { 3662 d dbabort [mc "session expiration"] $msg 3663 return [mc "Cannot un-register connection (%s)" $msg] 3664 } 3665 3666 # Log expired sessions 3667 3668 foreach e $lexp { 3669 lassign $e l tok la 3670 d writelog "auth" "lastaccess $l $tok" $la $l 3671 } 3672 3673 d dbcommit "session expiration" 3674 3675 # 3676 # Check our own authentication token 3677 # 3678 3679 set qtoken [::pgsql::quote $token] 3680 set login "" 3681 set found false 3682 set sql "UPDATE global.utmp t 3683 SET lastaccess = NOW() 3684 FROM global.nmuser u 3685 WHERE token = '$qtoken' AND u.idcor = t.idcor 3686 RETURNING u.login" 3687 pg_select $dbfd $sql tab { 3688 set login $tab(login) 3689 set found true 3690 } 3691 3692 if {$found} then { 3693 # re-inject cookie (for login/call-cgi) 3694 ::webapp::set-cookie "session" $token 0 "" "" 0 0 3695 } 3696 3697 return $found 3698} 3699 3700# 3701# Register a user login, create a session token and displays the start page 3702# 3703# Input: 3704# - parameters: 3705# - dbfd : database handle 3706# - login : the user for which we generate this token 3707# - casticket : service ticket returned by CAS server, or empty string 3708# Output: 3709# - return value: error message or empty string 3710# - database: token is registered in database 3711# 3712# History 3713# 2014/04/12 : pda : design 3714# 2015/01/21 : pda/jean : added idcor parameter 3715# 2015/02/25 : pda/jean : add code common to PGSQL/LDAP and CAS 3716# 2015/03/04 : pda/jean : register cas ticket 3717# 2015/06/05 : pda/jean : remove idcor parameter 3718# 3719 3720proc register-user-login {dbfd login casticket} { 3721 global env 3722 3723 # 3724 # Search id for the login 3725 # 3726 3727 set qlogin [::pgsql::quote $login] 3728 set idcor -1 3729 set sql "SELECT idcor 3730 FROM global.nmuser 3731 WHERE login = '$qlogin' 3732 AND present = 1" 3733 pg_select $dbfd $sql tab { 3734 set idcor $tab(idcor) 3735 } 3736 if {$idcor == -1} then { 3737 return [mc "Login '%s' does not exist" $login] 3738 } 3739 3740 # 3741 # Generates a unique (at a given time) token 3742 # In order to test if a generated token is already used, we search it 3743 # in the global.tmp template table (which gathers all utmp and wtmp 3744 # lines) 3745 # 3746 3747 d dblock {global.utmp} 3748 3749 set toklen [dnsconfig get "authtoklen"] 3750 3751 set found true 3752 while {$found} { 3753 set token [get-random $toklen] 3754 set sql "SELECT idcor FROM global.tmp WHERE token = '$token'" 3755 set found false 3756 pg_select $dbfd $sql tab { 3757 set found true 3758 } 3759 } 3760 3761 # 3762 # Register token in utmp table 3763 # 3764 3765 set ip NULL 3766 if {[info exists env(REMOTE_ADDR)]} then { 3767 set ip "'$env(REMOTE_ADDR)'" 3768 } 3769 set qcas NULL 3770 if {$casticket ne ""} then { 3771 set qcas [::pgsql::quote $casticket] 3772 set qcas "'$qcas'" 3773 } 3774 3775 set sql "INSERT INTO global.utmp (idcor, token, casticket, ip) 3776 VALUES ($idcor, '$token', $qcas, $ip)" 3777 if {! [::pgsql::execsql $dbfd $sql msg]} then { 3778 d dbabort [mc "session creation for %s" login] $msg 3779 return [mc "Cannot register user login (%s)" $msg] 3780 } 3781 3782 d dbcommit [mc "session creation for %s" $login] 3783 3784 # 3785 # Log successful flogin 3786 # 3787 3788 d writelog "auth" "login $login $token" 3789 3790 # 3791 # Set session cookie 3792 # 3793 3794 ::webapp::set-cookie "session" $token 0 "" "" 0 0 3795 3796 return "" 3797} 3798 3799proc register-user-logout {dbfd login token date reason} { 3800 set idcor -1 3801 set qlogin [::pgsql::quote $login] 3802 set sql "SELECT idcor FROM global.nmuser WHERE login = '$qlogin'" 3803 pg_select $dbfd $sql tab { 3804 set idcor $tab(idcor) 3805 } 3806 if {$idcor == -1} then { 3807 return [mc "Login '%s' does not exist" $login] 3808 } 3809 if {$date eq ""} then { 3810 set date "now()" 3811 } else { 3812 set date "'$date'" 3813 } 3814 3815 set sql "INSERT INTO global.wtmp (idcor, token, start, ip, stop, stopreason) 3816 SELECT $idcor, token, start, ip, $date, '$reason' 3817 FROM global.utmp 3818 WHERE idcor = $idcor and token = '$token' 3819 ; 3820 DELETE FROM global.utmp 3821 WHERE idcor = $idcor and token = '$token'" 3822 if {! [::pgsql::execsql $dbfd $sql msg]} then { 3823 return [mc "Cannot un-register connection (%s)" $msg] 3824 } 3825 return "" 3826} 3827 3828############################################################################## 3829# User access rights management 3830############################################################################## 3831 3832# 3833# Check login name validity 3834# 3835# Input: 3836# - parameters: 3837# - login : login name 3838# Output: 3839# - return value: 1 (valid) or 0 (invalid) 3840# 3841# History 3842# 2015/05/07 : pda/jean : design 3843# 3844 3845proc check-login {name} { 3846 return [expr ! [regexp {[()<>*]} $name]] 3847} 3848 3849# 3850# Search attributes associated to a user 3851# 3852# Input: 3853# - parameters: 3854# - dbfd : database handle 3855# - idcor : user id 3856# - attr : attribute to check (table column) 3857# Output: 3858# - return value: information found 3859# 3860# History 3861# 2000/07/26 : pda : design 3862# 2002/05/03 : pda/jean : use in netmagis 3863# 2002/05/06 : pda/jean : groups 3864# 2010/11/29 : pda : i18n 3865# 3866 3867proc user-attribute {dbfd idcor attr} { 3868 set v 0 3869 set sql "SELECT nmgroup.$attr 3870 FROM global.nmgroup, global.nmuser 3871 WHERE nmuser.idcor = $idcor 3872 AND nmuser.idgrp = nmgroup.idgrp" 3873 pg_select $dbfd $sql tab { 3874 set v "$tab($attr)" 3875 } 3876 return $v 3877} 3878 3879# 3880# Read informations associated to a user 3881# 3882# Input: 3883# - parameters: 3884# - dbfd : database handle 3885# - login : user login 3886# - _tabuid : array containing, in return: 3887# login login of the user 3888# lastname user name [if ah global variable is set] 3889# firstname user first name [if ah global variable is set] 3890# mail user mail [if ah global variable is set] 3891# phone user phone [if ah global variable is set] 3892# mobile user mobile phone [if ah global variable is set] 3893# fax user fax [if ah global variable is set] 3894# addr user address [if ah global variable is set] 3895# idcor user id in the database 3896# idgrp group id in the database 3897# group group name 3898# present 1 if "present" in the database 3899# p_admin 1 if admin 3900# p_smtp 1 if permission to allow hosts to emit with SMTP 3901# p_ttl 1 if permission to edit host TTL 3902# p_mac 1 if permission to use the MAC module 3903# p_genl 1 if permission to generate a link number 3904# networks list of authorized networks 3905# eq regexp matching authorized equipments 3906# flagsr flags -n/-e/-E/-v/etc to use in topo programs 3907# flagsw flags -n/-e/-E/-v/etc to use in topo programs 3908# Output: 3909# - return value: -1 if error, or number of found entries 3910# - parameter _tabuid : values in return 3911# - parameter _msg : empty string (if return == 1) or message (if return != 1) 3912# 3913# History 3914# 2003/05/13 : pda/jean : design 3915# 2007/10/05 : pda/jean : adaptation to "authuser" and "authbase" objects 3916# 2010/11/09 : pda : renaming (car plus de recherche par id) 3917# 2010/11/29 : pda : i18n 3918# 2011/06/17 : pda : add test on ah global variable 3919# 2012/01/21 : jean : add generate link number permission 3920# 3921 3922proc read-user {dbfd login _tabuid _msg} { 3923 global ah 3924 upvar $_tabuid tabuid 3925 upvar $_msg msg 3926 3927 catch {unset tabuid} 3928 3929 if {$ah ne ""} then { 3930 # 3931 # Attributes common to all applications 3932 # 3933 3934 set u [::webapp::authuser create %AUTO%] 3935 if {[catch {set n [$ah getuser $login $u]} m]} then { 3936 set msg [mc "Authentication base problem: %s" $m] 3937 return -1 3938 } 3939 3940 switch $n { 3941 0 { 3942 set msg [mc "User '%s' is not in the authentication base" $login] 3943 return 0 3944 } 3945 1 { 3946 set msg "" 3947 } 3948 default { 3949 set msg [mc "Found more than one entry for login '%s' in the authentication base" $login] 3950 return $n 3951 } 3952 } 3953 3954 foreach c {login password lastname firstname mail phone mobile fax addr} { 3955 set tabuid($c) [$u get $c] 3956 } 3957 3958 $u destroy 3959 } 3960 3961 # 3962 # Netmagis specific characteristics 3963 # 3964 3965 set qlogin [::pgsql::quote $login] 3966 set tabuid(idcor) -1 3967 set sql "SELECT * FROM global.nmuser, global.nmgroup 3968 WHERE nmuser.login = '$qlogin' 3969 AND nmuser.idgrp = nmgroup.idgrp" 3970 pg_select $dbfd $sql tab { 3971 set tabuid(idcor) $tab(idcor) 3972 set tabuid(idgrp) $tab(idgrp) 3973 set tabuid(present) $tab(present) 3974 set tabuid(group) $tab(name) 3975 set tabuid(p_admin) $tab(p_admin) 3976 set tabuid(p_smtp) $tab(p_smtp) 3977 set tabuid(p_ttl) $tab(p_ttl) 3978 set tabuid(p_mac) $tab(p_mac) 3979 set tabuid(p_genl) $tab(p_genl) 3980 } 3981 3982 if {$tabuid(idcor) == -1} then { 3983 set msg [mc "User '%s' is not in the Netmagis base" $login] 3984 return 0 3985 } 3986 3987 # 3988 # Topo specific characteristics 3989 # 3990 3991 # Read authorized L2-only networks 3992 set tabuid(l2only) [allowed-l2only $dbfd $tabuid(idgrp)] 3993 3994 # Read authorized CIDR 3995 set tabuid(networks) [allowed-networks $dbfd $tabuid(idgrp) "consult"] 3996 3997 # Read regexp to allow or deny access to equipments 3998 set tabuid(eqr) [read-authorized-eq $dbfd 0 $tabuid(idgrp)] 3999 set tabuid(eqw) [read-authorized-eq $dbfd 1 $tabuid(idgrp)] 4000 4001 # Build flags to restrict graph to a subset according to 4002 # user rights. 4003 set flagsr {} 4004 set flagsw {} 4005 foreach rw {r w} { 4006 set flags {} 4007 if {$tabuid(p_admin)} then { 4008 # Administrator sees the whole graph 4009 lappend flags "-a" 4010 4011 # Even if he sees the whole graph, administrator has not 4012 # the right to modify non terminal interfaces 4013 if {$rw eq "w"} then { 4014 lappend flags "-t" 4015 } 4016 4017 } else { 4018 lassign $tabuid(eq$rw) lallow ldeny 4019 4020 # Build networks rights first: the user has access to 4021 # all interfaces that "his" networks reach (except if 4022 # has no right on an equipment) 4023 foreach r $tabuid(networks) { 4024 set r4 [lindex $r 1] 4025 if {$r4 ne ""} then { 4026 lappend flags "-n" $r4 4027 } 4028 set r6 [lindex $r 2] 4029 if {$r6 ne ""} then { 4030 lappend flags "-n" $r6 4031 } 4032 } 4033 4034 # Next, build access rights on L2-only networks 4035 foreach vlan $tabuid(l2only) { 4036 lappend flags "-v" $vlan 4037 } 4038 4039 # Next, build access rights on equipements (part 1) 4040 # The user has access to the whole equipment (including 4041 # interfaces) 4042 foreach pat $lallow { 4043 lappend flags "-e" $pat 4044 } 4045 4046 # Next, build access rights on equipements (part 2) 4047 # The user has no access to the whole equipment, even 4048 # if some parts (equipement or interfaces reached by 4049 # a network) have been selected previously). 4050 foreach pat $ldeny { 4051 lappend flags "-E" $pat 4052 } 4053 4054 # Last, the user don't have right to modify: 4055 # - non terminal interfaces 4056 # - interfaces which transport a foreign network 4057 if {$rw eq "w"} then { 4058 lappend flags "-t" "-m" 4059 } 4060 } 4061 set tabuid(flags$rw) [join $flags " "] 4062 } 4063 4064 return 1 4065} 4066 4067############################################################################## 4068# Database management : resources records 4069############################################################################## 4070 4071# 4072# Return all RR with a given name (in different views) 4073# 4074# Input: 4075# - parameters: 4076# - dbfd : database handle 4077# - name : name to search for 4078# - iddom : id of the domain in which to search for the name 4079# Output: 4080# - return value: { {idrr idview} {idrr idview} ...} 4081# 4082# History 4083# 2013/04/05 : pda/jean : design 4084# 4085 4086proc all-rr-by-name {dbfd name iddom} { 4087 set qname [::pgsql::quote $name] 4088 set sql "SELECT idrr, idview FROM dns.rr 4089 WHERE name = '$qname' AND iddom = $iddom" 4090 set l {} 4091 pg_select $dbfd $sql tab { 4092 lappend l [list $tab(idrr) $tab(idview)] 4093 } 4094 4095 return $l 4096} 4097 4098# 4099# Get all informations associated with a name 4100# 4101# Input: 4102# - parameters: 4103# - dbfd : database handle 4104# - name : name to search for 4105# - iddom : id of the domain in which to search for the name 4106# - idview: view id 4107# - _trr : empty array 4108# Output: 4109# - return value: 1 if ok, 0 if not found 4110# - _trr parameter : see read-rr-by-id 4111# 4112# History 4113# 2002/04/11 : pda/jean : design 4114# 2002/04/19 : pda/jean : add name and iddom 4115# 2002/04/19 : pda/jean : use read-rr-by-id 4116# 2010/11/29 : pda : i18n 4117# 2013/04/05 : pda/jean : add view 4118# 4119 4120proc read-rr-by-name {dbfd name iddom idview _trr} { 4121 upvar $_trr trr 4122 4123 set qname [::pgsql::quote $name] 4124 set found 0 4125 set sql "SELECT idrr FROM dns.rr 4126 WHERE name = '$qname' 4127 AND iddom = $iddom 4128 AND idview = $idview" 4129 pg_select $dbfd $sql tab { 4130 set found 1 4131 set idrr $tab(idrr) 4132 } 4133 4134 if {$found} then { 4135 set found [read-rr-by-id $dbfd $idrr trr] 4136 } 4137 4138 return $found 4139} 4140 4141# 4142# Get all informations associated with a RR given by the MAC Address 4143# 4144# Input: 4145# - parameters: 4146# - dbfd : database handle 4147# - addr : address to search for 4148# - _trr : empty array 4149# Output: 4150# - return value: 1 if ok, 0 if not found 4151# - _trr parameter : see read-rr-by-id 4152# 4153# Note: the given address is supposed to be syntaxically correct. 4154# 4155# History 4156# 2012/04/28 : jean : integrated patch from Benoit.Mandy@u-bordeaux4.fr 4157# 4158 4159proc read-rr-by-mac {dbfd addr _trr} { 4160 upvar $_trr trr 4161 4162 set found 0 4163 set sql "SELECT idrr FROM dns.rr WHERE mac = '$addr'" 4164 pg_select $dbfd $sql tab { 4165 set found 1 4166 set idrr $tab(idrr) 4167 } 4168 4169 if {$found} then { 4170 set found [read-rr-by-id $dbfd $idrr trr] 4171 } 4172 4173 return $found 4174} 4175 4176 4177# 4178# Get all informations associated with a RR given by one of its IP address 4179# 4180# Input: 4181# - parameters: 4182# - dbfd : database handle 4183# - addr : address to search for 4184# - idview : id of view 4185# - _trr : empty array 4186# Output: 4187# - return value: 1 if ok, 0 if not found 4188# - _trr parameter : see read-rr-by-id 4189# 4190# Note: the given address is supposed to be syntaxically correct. 4191# 4192# History 4193# 2002/04/26 : pda/jean : design 4194# 2010/11/29 : pda : i18n 4195# 4196 4197proc read-rr-by-ip {dbfd addr idview _trr} { 4198 upvar $_trr trr 4199 4200 set found 0 4201 set sql "SELECT i.idrr 4202 FROM dns.rr_ip i, dns.rr r 4203 WHERE i.idrr = r.idrr 4204 AND i.addr = '$addr' 4205 AND r.idview = $idview" 4206 pg_select $dbfd $sql tab { 4207 set found 1 4208 set idrr $tab(idrr) 4209 } 4210 4211 if {$found} then { 4212 set found [read-rr-by-id $dbfd $idrr trr] 4213 } 4214 4215 return $found 4216} 4217 4218# 4219# Get all informations associated with a RR. 4220# 4221# Input: 4222# - parameters: 4223# - dbfd : database handle 4224# - idrr : RR id to search for 4225# - _trr : empty array 4226# Output: 4227# - return value: 1 if ok, 0 if not found 4228# - parameter _trr : 4229# _trr(idrr) : id of RR found 4230# _trr(name) : name (first component of the FQDN) 4231# _trr(iddom) : domain id 4232# _trr(idview) : view id 4233# _trr(domain) : domain name 4234# _trr(mac) : MAC address 4235# _trr(iddhcpprof) : DHCP profile id, or 0 if none 4236# _trr(dhcpprof) : DHCP profile name, or "No profile" 4237# _trr(idhinfo) : machine info id 4238# _trr(hinfo) : machine info text 4239# _trr(sendsmtp) : 1 if host has the right to emit with non auth SMTP 4240# _trr(ttl) : TTL of the host (for all its IP addresses) 4241# _trr(comment) : comments 4242# _trr(respname) : name of the responsible person 4243# _trr(respmail) : mail of the responsible person 4244# _trr(idcor) : id of user who has done the last modification 4245# _trr(date) : date of last modification 4246# _trr(ip) : list of all IP addresses {{idview addr} ...} 4247# _trr(mx) : MX list {{idview prio idrr} {idview prio idrr} ...} 4248# _trr(mxtarg) : list of MX which target this host 4249# _trr(cname) : list of pointed RR, if name is an alias {{idview idrr}...} 4250# _trr(aliases) : list of all RR pointing to this object {{idview idrr}..} 4251# _trr(mailrole) : id of mbox host {{idview idmboxhost idviewmbox} ...} 4252# _trr(mailaddr) : idrr of mail addresses hosted on this host 4253# {{idview idmailaddr idviewmailaddr} ...} 4254# 4255# History 4256# 2002/04/19 : pda/jean : design 4257# 2002/06/02 : pda/jean : hinfo becomes an index in a table 4258# 2004/02/06 : pda/jean : add mailrole, mailaddr and roleweb 4259# 2004/08/05 : pda/jean : simplification and add mac 4260# 2005/04/08 : pda/jean : add dhcpprofil 4261# 2008/07/24 : pda/jean : add sendsmtp 4262# 2010/10/31 : pda : add ttl 4263# 2010/11/29 : pda : i18n 4264# 2012/10/08 : pda/jean : views 4265# 2013/04/05 : pda/jean : temporary hack for views 4266# 2013/04/10 : pda/jean : remove roleweb 4267# 4268 4269proc read-rr-by-id {dbfd idrr _trr} { 4270 upvar $_trr trr 4271 4272 set fields {name iddom idview 4273 mac iddhcpprof idhinfo sendsmtp ttl comment respname respmail 4274 idcor date} 4275 4276 catch {unset trr} 4277 set trr(idrr) $idrr 4278 4279 set found 0 4280 set columns [join $fields ", "] 4281 set sql "SELECT $columns FROM dns.rr WHERE idrr = $idrr" 4282 pg_select $dbfd $sql tab { 4283 set found 1 4284 foreach v $fields { 4285 set trr($v) $tab($v) 4286 } 4287 } 4288 4289 if {$found} then { 4290 set idview $trr(idview) 4291 set trr(domain) "" 4292 if {$trr(iddhcpprof) eq ""} then { 4293 set trr(iddhcpprof) 0 4294 set trr(dhcpprof) [mc "No profile"] 4295 } else { 4296 set sql "SELECT name FROM dns.dhcpprofile 4297 WHERE iddhcpprof = $trr(iddhcpprof)" 4298 pg_select $dbfd $sql tab { 4299 set trr(dhcpprof) $tab(name) 4300 } 4301 } 4302 set sql "SELECT name FROM dns.hinfo WHERE idhinfo = $trr(idhinfo)" 4303 pg_select $dbfd $sql tab { 4304 set trr(hinfo) $tab(name) 4305 } 4306 set sql "SELECT name FROM dns.domain WHERE iddom = $trr(iddom)" 4307 pg_select $dbfd $sql tab { 4308 set trr(domain) $tab(name) 4309 } 4310 set trr(ip) {} 4311 set sql "SELECT addr FROM dns.rr_ip WHERE idrr = $idrr" 4312 pg_select $dbfd $sql tab { 4313 lappend trr(ip) [list $idview $tab(addr)] 4314 } 4315 set trr(mx) {} 4316 set sql "SELECT prio, mx FROM dns.rr_mx WHERE idrr = $idrr" 4317 pg_select $dbfd $sql tab { 4318 lappend trr(mx) [list $idview $tab(prio) $tab(mx)] 4319 } 4320 set trr(mxtarg) {} 4321 set sql "SELECT idrr FROM dns.rr_mx WHERE mx = $idrr" 4322 pg_select $dbfd $sql tab { 4323 lappend trr(mxtarg) [list $idview $tab(idrr)] 4324 } 4325 set trr(cname) "" 4326 set sql "SELECT cname FROM dns.rr_cname WHERE idrr = $idrr" 4327 pg_select $dbfd $sql tab { 4328 lappend trr(cname) [list $idview $tab(cname)] 4329 } 4330 set trr(aliases) {} 4331 set sql "SELECT idrr FROM dns.rr_cname WHERE cname = $idrr" 4332 pg_select $dbfd $sql tab { 4333 lappend trr(aliases) [list $idview $tab(idrr)] 4334 } 4335 # is this name a mail address? 4336 set trr(mailrole) "" 4337 set sql "SELECT mr.mboxhost, rrmb.idview AS idviewmbox 4338 FROM dns.mail_role mr, dns.rr rrmb 4339 WHERE mr.mailaddr = $idrr 4340 AND mr.mboxhost = rrmb.idrr" 4341 pg_select $dbfd $sql tab { 4342 lappend trr(mailrole) [list $idview $tab(mboxhost) $tab(idviewmbox)] 4343 } 4344 # all mail addresses pointing to this host 4345 set trr(mailaddr) {} 4346 set sql "SELECT rrma.idrr AS idrrma, rrma.idview AS idviewma 4347 FROM dns.mail_role mr, dns.rr rrma 4348 WHERE mboxhost = $idrr 4349 AND mr.mailaddr = rrma.idrr" 4350 pg_select $dbfd $sql tab { 4351 lappend trr(mailaddr) [list $idview $tab(idrrma) $tab(idviewma)] 4352 } 4353 } 4354 4355 return $found 4356} 4357 4358# 4359# Get RR information filtered for a view 4360# 4361# Input: 4362# - parameters: 4363# - _trr : see read-rr-by-id 4364# - idview : view 4365# Output: 4366# - return value: list of IP addresses 4367# 4368# History 4369# 2012/10/08 : pda/jean : design 4370# 4371 4372proc rr-ip-by-view {_trr idview} { 4373 upvar $_trr trr 4374 4375 set lip {} 4376 if {[info exists trr(ip)]} then { 4377 foreach ipview $trr(ip) { 4378 lassign $ipview id ip 4379 if {$id == $idview} then { 4380 lappend lip $ip 4381 } 4382 } 4383 } 4384 return $lip 4385} 4386 4387proc rr-cname-by-view {_trr idview} { 4388 upvar $_trr trr 4389 4390 set r "" 4391 if {[info exists trr(cname)]} then { 4392 foreach cv $trr(cname) { 4393 lassign $cv id cname 4394 if {$id == $idview} then { 4395 set r $cname 4396 break 4397 } 4398 } 4399 } 4400 return $r 4401} 4402 4403proc rr-aliases-by-view {_trr idview} { 4404 upvar $_trr trr 4405 4406 set laliases {} 4407 if {[info exists trr(aliases)]} then { 4408 foreach alview $trr(aliases) { 4409 lassign $alview id idalias 4410 if {$id == $idview} then { 4411 lappend laliases $idalias 4412 } 4413 } 4414 } 4415 return $laliases 4416} 4417 4418proc rr-mx-by-view {_trr idview} { 4419 upvar $_trr trr 4420 4421 set lmx {} 4422 if {[info exists trr(mx)]} then { 4423 foreach mxview $trr(mx) { 4424 lassign $mxview id prio idrr 4425 if {$id == $idview} then { 4426 lappend lmx [list $prio $idrr] 4427 } 4428 } 4429 } 4430 return $lmx 4431} 4432 4433proc rr-mxtarg-by-view {_trr idview} { 4434 upvar $_trr trr 4435 4436 set lmxt {} 4437 if {[info exists trr(mxtarg)]} then { 4438 foreach mxview $trr(mxtarg) { 4439 lassign $mxview id idrr 4440 if {$id == $idview} then { 4441 lappend lmxt $idrr 4442 } 4443 } 4444 } 4445 return $lmxt 4446} 4447 4448proc rr-mailrole-by-view {_trr idview} { 4449 upvar $_trr trr 4450 4451 set lrm {} 4452 if {[info exists trr(mailrole)]} then { 4453 foreach rmview $trr(mailrole) { 4454 lassign $rmview id idmboxhost idviewmboxhost 4455 if {$id == $idview} then { 4456 set lrm [list $idmboxhost $idviewmboxhost] 4457 } 4458 } 4459 } 4460 return $lrm 4461} 4462 4463proc rr-mailaddr-by-view {_trr idview} { 4464 upvar $_trr trr 4465 4466 set lam {} 4467 if {[info exists trr(mailaddr)]} then { 4468 foreach amview $trr(mailaddr) { 4469 lassign $amview id idrraddr idviewaddr 4470 if {$id == $idview} then { 4471 lappend lam [list $idrraddr $idviewaddr] 4472 } 4473 } 4474 } 4475 return $lam 4476} 4477 4478# 4479# Delete an alias 4480# 4481# Input: 4482# - parameters: 4483# - dbfd : database handle 4484# - idrr : id of RR to delete (CNAME RR) 4485# - _msg : error message in return 4486# Output: 4487# - return value: error message or empty string 4488# 4489# History 4490# 2002/04/19 : pda/jean : design 4491# 2010/11/29 : pda : i18n 4492# 2012/11/13 : pda/jean : add views 4493# 2013/03/28 : pda/jean : interface simplification 4494# 2013/04/10 : pda/jean : remove views 4495# 4496 4497proc del-alias-by-id {dbfd idrr} { 4498 set msg "" 4499 set sql "DELETE FROM dns.rr_cname WHERE idrr = $idrr" 4500 if {[::pgsql::execsql $dbfd $sql msg]} then { 4501 set msg [del-orphaned-rr $dbfd $idrr] 4502 } 4503 return $msg 4504} 4505 4506# 4507# Delete all IP address for a RR 4508# 4509# Input: 4510# - parameters: 4511# - dbfd : database handle 4512# - idrr : RR id 4513# Output: 4514# - return value: error message or empty string 4515# 4516# History 4517# 2002/04/19 : pda/jean : design 4518# 2010/11/29 : pda : i18n 4519# 2012/11/13 : pda/jean : add views 4520# 2012/11/14 : pda/jean : delete addr parameter 4521# 2013/03/28 : pda/jean : interface simplification 4522# 2013/04/10 : pda/jean : remove views 4523# 4524 4525proc del-all-ip-addresses {dbfd idrr} { 4526 set msg "" 4527 set sql "DELETE FROM dns.rr_ip WHERE idrr = $idrr" 4528 if {[::pgsql::execsql $dbfd $sql msg]} then { 4529 set msg "" 4530 } 4531 return $msg 4532} 4533 4534# 4535# Delet all MX associated with an RR 4536# 4537# Input: 4538# - parameters: 4539# - dbfd : database handle 4540# - idrr : RR id of MX 4541# Output: 4542# - return value: error message or empty string 4543# 4544# History 4545# 2002/04/19 : pda/jean : design 4546# 2010/11/29 : pda : i18n 4547# 2012/11/13 : pda/jean : add views 4548# 2013/03/28 : pda/jean : interface simplification 4549# 2013/04/10 : pda/jean : remove views 4550# 4551 4552proc del-mx-by-id {dbfd idrr} { 4553 set msg "" 4554 set sql "DELETE FROM dns.rr_mx WHERE idrr = $idrr" 4555 if {[::pgsql::execsql $dbfd $sql msg]} then { 4556 set msg "" 4557 } 4558 return $msg 4559} 4560 4561# 4562# Delete a mailrole 4563# 4564# Input: 4565# - parameters: 4566# - dbfd : database handle 4567# - idrr : RR id 4568# Output: 4569# - return value: error message or empty string 4570# 4571# History 4572# 2004/02/06 : pda/jean : design 4573# 2010/11/29 : pda : i18n 4574# 2012/11/13 : pda/jean : add views 4575# 2013/03/28 : pda/jean : interface simplification 4576# 2013/04/10 : pda/jean : remove views 4577# 4578 4579proc del-mailrole-by-id {dbfd idrr} { 4580 set msg "" 4581 set sql "DELETE FROM dns.mail_role WHERE mailaddr = $idrr" 4582 if {[::pgsql::execsql $dbfd $sql msg]} then { 4583 set msg "" 4584 } 4585 return $msg 4586} 4587 4588# 4589# Delete an RR and all associated dependancies 4590# 4591# Input: 4592# - parameters: 4593# - dbfd : database handle 4594# - _trr : RR informations (see read-rr-by-id) 4595# Output: 4596# - return value: error message or empty string 4597# 4598# History 4599# 2002/04/19 : pda/jean : design 4600# 2004/02/06 : pda/jean : add mailrole and roleweb 4601# 2010/11/29 : pda : i18n 4602# 2012/11/13 : pda/jean : add views 4603# 2013/03/28 : pda/jean : interface simplification 4604# 2013/04/10 : pda/jean : remove views 4605# 4606 4607proc del-rr-and-dependancies {dbfd _trr} { 4608 upvar $_trr trr 4609 4610 set idrr $trr(idrr) 4611 set idview $trr(idview) 4612 4613 # 4614 # If this host holds mail addresses, don't delete it. 4615 # 4616 4617 set mailaddr [rr-mailaddr-by-view trr $idview] 4618 if {[llength $mailaddr] > 0} then { 4619 return [mc "This host holds mail addresses"] 4620 } 4621 4622 # 4623 # If this host is the target of any MX, don't delete it. 4624 # 4625 4626 set mxt [rr-mxtarg-by-view trr $idview] 4627 if {[llength $mxt] > 0} then { 4628 return [mc "This host is the target of one or more MX"] 4629 } 4630 set sql "SELECT COUNT(*) FROM dns.relay_dom WHERE mx = $idrr" 4631 pg_select $dbfd $sql tab { 4632 set count $tab(count) 4633 } 4634 if {$count > 0} then { 4635 return [mc "This host is the mail relay of one or more domains"] 4636 } 4637 4638 # 4639 # Delete all aliases pointing to this object 4640 # 4641 4642 foreach a [rr-aliases-by-view trr $idview] { 4643 set msg [del-alias-by-id $dbfd $a] 4644 if {$msg ne ""} then { 4645 return $msg 4646 } 4647 } 4648 4649 # 4650 # Delete all IP addresses 4651 # 4652 4653 set msg [del-all-ip-addresses $dbfd $idrr] 4654 if {$msg ne ""} then { 4655 return $msg 4656 } 4657 4658 # 4659 # Delete all MX 4660 # 4661 4662 set msg [del-mx-by-id $dbfd $idrr] 4663 if {$msg ne ""} then { 4664 return $msg 4665 } 4666 4667 # 4668 # Delete the RR itself (if possible) 4669 # 4670 4671 set msg [del-orphaned-rr $dbfd $idrr] 4672 if {$msg ne ""} then { 4673 return $msg 4674 } 4675 4676 # 4677 # Finished ! 4678 # 4679 4680 return "" 4681} 4682 4683# 4684# Delete an RR if nothing points to it (IP address, alias, mail domain, etc.) 4685# 4686# Input: 4687# - parameters: 4688# - dbfd : database handle 4689# - idrr : RR id 4690# Output: 4691# - return value: empty string or error message 4692# 4693# Note : if the RR is not an orphaned one, it is not deleted and 4694# an empty string is returned (it is a normal case, not an error). 4695# 4696# History 4697# 2004/02/13 : pda/jean : design 4698# 2010/11/29 : pda : i18n 4699# 4700 4701proc del-orphaned-rr {dbfd idrr} { 4702 set msg "" 4703 if {[read-rr-by-id $dbfd $idrr trr]} then { 4704 set orphaned 1 4705 foreach x {ip mx aliases cname mailrole mailaddr} { 4706 if {$trr($x) ne ""} then { 4707 set orphaned 0 4708 break 4709 } 4710 } 4711 4712 if {$orphaned} then { 4713 set sql "DELETE FROM dns.rr WHERE idrr = $idrr" 4714 if {[::pgsql::execsql $dbfd $sql msg]} then { 4715 # it worked, but this function may have modified "msg" 4716 set msg "" 4717 } 4718 } 4719 } 4720 return $msg 4721} 4722 4723# 4724# Add a new RR 4725# 4726# Input: 4727# - parameters: 4728# - dbfd : database handle 4729# - name : name of RR to create (syntax must be conform to RFC) 4730# - iddom : domain id 4731# - idview: view id 4732# - mac : MAC address, or empty string 4733# - iddhcpprof : DHCP profile id, or 0 4734# - idhinfo : HINFO or empty string (default is searched in the database) 4735# - sendsmtp : 1 if ok to emit with non auth SMTP 4736# - ttl : TTL value, or -1 for default value 4737# - comment : commment 4738# - respname : responsible person name 4739# - respmail : responsible person mail 4740# - idcor : user id 4741# - _trr : in return, will contain RR information 4742# Output: 4743# - return value: empty string, or error message 4744# - parameter _trr : see read-rr-by-id 4745# 4746# Warning: name syntax is supposed to be valid. Do not forget to call 4747# check-name-syntax before calling this function. 4748# 4749# History 4750# 2004/02/13 : pda/jean : design 4751# 2004/08/05 : pda/jean : add mac 4752# 2004/10/05 : pda : change date format 4753# 2005/04/08 : pda/jean : add dhcpprofil 4754# 2008/07/24 : pda/jean : add sendsmtp 4755# 2010/10/31 : pda : add ttl 4756# 2010/11/29 : pda : i18n 4757# 2013/04/05 : pda/jean : add views 4758# 4759 4760proc add-rr {dbfd name iddom idview mac iddhcpprof idhinfo sendsmtp ttl 4761 comment respname respmail idcor _trr} { 4762 upvar $_trr trr 4763 4764 if {$mac eq ""} then { 4765 set qmac NULL 4766 } else { 4767 set qmac "'[::pgsql::quote $mac]'" 4768 } 4769 set qcomment [::pgsql::quote $comment] 4770 set qrespname [::pgsql::quote $respname] 4771 set qrespmail [::pgsql::quote $respmail] 4772 set hinfodef "" 4773 set hinfoval "" 4774 if {$idhinfo ne ""} then { 4775 set hinfodef "idhinfo," 4776 set hinfoval "$idhinfo, " 4777 } 4778 if {$iddhcpprof == 0} then { 4779 set iddhcpprof NULL 4780 } 4781 set sql "INSERT INTO dns.rr 4782 (name, iddom, idview, mac, iddhcpprof, $hinfodef 4783 sendsmtp, ttl, comment, respname, respmail, 4784 idcor) 4785 VALUES 4786 ('$name', $iddom, $idview, $qmac, $iddhcpprof, $hinfoval 4787 $sendsmtp, $ttl, 4788 '$qcomment', '$qrespname', '$qrespmail', 4789 $idcor) 4790 " 4791 if {[::pgsql::execsql $dbfd $sql msg]} then { 4792 set msg "" 4793 if {! [read-rr-by-name $dbfd $name $iddom $idview trr]} then { 4794 set msg [mc "Internal error: '%s' inserted, but not found in database" $name] 4795 4796 } 4797 } else { 4798 set msg [mc "RR addition impossible: %s" $msg] 4799 } 4800 return $msg 4801} 4802 4803# 4804# Add host 4805# 4806# Input: 4807# - parameters: 4808# - dbfd : database handle 4809# - _trr: trr of existing fqdn or empty trr 4810# - name : name of RR to create (syntax must be conform to RFC) 4811# - iddom : domain id 4812# - idview: view id 4813# - addr: (single) IP address to add 4814# - mac : MAC address, or empty string 4815# - iddhcpprof : DHCP profile id, or 0 4816# - idhinfo : idhinfo (0 for default value) 4817# - sendsmtp : 1 if ok to emit with non auth SMTP 4818# - ttl : TTL value, or -1 for default value 4819# - comment : commment 4820# - respname : responsible person name 4821# - respmail : responsible person mail 4822# - idcor : user id 4823# Output: 4824# - return value: empty string, or error message 4825# - parameter _trr: completed RR 4826# 4827# History 4828# 2013/03/28 : pda/jean : shared code between www/cgi/ and utils/ 4829# 2013/04/10 : pda/jean : accept only one view 4830# 4831 4832proc add-host {dbfd _trr name iddom idview addr mac iddhcpprof idhinfo sendsmtp ttl comment respname respmail idcor} { 4833 upvar $_trr trr 4834 4835 # 4836 # Handle one of two cases: 4837 # - object does not have an IP address, or 4838 # - it have IP address(es) and user has confirmed 4839 # Insert object in database : (RR + IP addr) or only (IP addr) 4840 # 4841 4842 d dblock {dns.rr dns.rr_ip} 4843 4844 if {$trr(idrr) == ""} then { 4845 # 4846 # Name did not exist, thus we insert a new RR 4847 # 4848 set msg [add-rr $dbfd $name $iddom $idview \ 4849 $mac $iddhcpprof $idhinfo $sendsmtp $ttl \ 4850 $comment $respname $respmail $idcor trr] 4851 if {$msg ne ""} then { 4852 d dbabort [mc "add %s" $name] $msg 4853 } 4854 4855 } else { 4856 # 4857 # RR was existing. Host informations may have been modified. 4858 # Update only if needed. 4859 # 4860 4861 if {$trr(ip) eq ""} then { 4862 # 4863 # Addition to an existing RR (eg: declare a host when 4864 # only mail role was existing). 4865 # 4866 if {! ($mac eq $trr(mac) 4867 && $iddhcpprof eq $trr(iddhcpprof) 4868 && $idhinfo eq $trr(idhinfo) 4869 && $sendsmtp eq $trr(sendsmtp) 4870 && $ttl eq $trr(ttl) 4871 && $comment eq $trr(comment) 4872 && $respname eq $trr(respname) 4873 && $respmail eq $trr(respmail))} then { 4874 if {$mac eq ""} then { 4875 set qmac NULL 4876 } else { 4877 set qmac "'[::pgsql::quote $mac]'" 4878 } 4879 set qcomment [::pgsql::quote $comment] 4880 set qrespname [::pgsql::quote $respname] 4881 set qrespmail [::pgsql::quote $respmail] 4882 if {$iddhcpprof == 0} then { 4883 set iddhcpprof NULL 4884 } 4885 set sql "UPDATE dns.rr SET 4886 mac = $qmac, 4887 iddhcpprof = $iddhcpprof, 4888 idhinfo = $idhinfo, 4889 sendsmtp = $sendsmtp, 4890 ttl = $ttl, 4891 comment = '$qcomment', 4892 respname = '$qrespname', 4893 respmail = '$qrespmail' 4894 WHERE idrr = $trr(idrr)" 4895 if {! [::pgsql::execsql $dbfd $sql msg]} then { 4896 d dbabort [mc "modify %s" [mc "host information"]] $msg 4897 } 4898 } 4899 } 4900 } 4901 4902 set sql "INSERT INTO dns.rr_ip (idrr, addr) VALUES ($trr(idrr), '$addr')" 4903 if {! [::pgsql::execsql $dbfd $sql msg]} then { 4904 d dbabort [mc "add %s" $addr] $msg 4905 } 4906 4907 # 4908 # Keep a note about user 4909 # 4910 4911 set msg [touch-rr $dbfd $trr(idrr)] 4912 if {$msg ne ""} then { 4913 d dbabort [mc "modify %s" [mc "RR"]] $msg 4914 } 4915 4916 set domain [u domainname $iddom] 4917 4918 d dbcommit [mc "add %s" "$name.$domain"] 4919 d writelog "addhost" "add $name.$domain ($addr)/[u viewname $idview]" 4920 4921 return "" 4922} 4923 4924# 4925# Add alias 4926# 4927# Input: 4928# - parameters: 4929# - dbfd : database handle 4930# - _trr: trr of existing fqdn or empty trr 4931# - name : name of RR to create (syntax must be conform to RFC) 4932# - domain : domain name 4933# - idview: view id 4934# - nameref: name of existing host 4935# - domainref: domain name of existing host 4936# - idcor : user id 4937# Output: 4938# - return value: empty string, or error message 4939# - parameter _trr: completed RR 4940# 4941# History 4942# 2013/03/28 : pda/jean : shared code between www/cgi/ and utils/ 4943# 2013/04/10 : pda/jean : accept only one view 4944# 4945 4946proc add-alias {dbfd name domain idview nameref domainref idcor} { 4947 # 4948 # Check alias and host permissions 4949 # 4950 4951 set msg [check-authorized-host $dbfd $idcor $name $domain $idview trr "alias"] 4952 if {$msg ne ""} then { 4953 return $msg 4954 } 4955 set iddom $trr(iddom) 4956 4957 set msg [check-authorized-host $dbfd $idcor $nameref $domainref $idview trrref "existing-host"] 4958 if {$msg ne ""} then { 4959 return $msg 4960 } 4961 4962 # 4963 # All test are ok, we just have to insert new alias 4964 # 4965 4966 d dblock {dns.rr dns.rr_cname} 4967 4968 # 4969 # This name was unknown, insert a new RR for new alias name 4970 # 4971 4972 if {$trr(idrr) eq ""} then { 4973 set msg [add-rr $dbfd $name $iddom $idview "" 0 "" 0 -1 "" "" "" $idcor trr] 4974 if {$msg ne ""} then { 4975 d dbabort [mc "add %s" $name] $msg 4976 } 4977 } 4978 4979 # 4980 # Add alias link between alias and host 4981 # 4982 4983 set sql "INSERT INTO dns.rr_cname (idrr, cname) 4984 VALUES ($trr(idrr), $trrref(idrr))" 4985 if {! [::pgsql::execsql $dbfd $sql msg]} then { 4986 d dbabort [mc "add %s" [mc "alias"]] $msg 4987 } 4988 4989 d dbcommit [mc "add %s" "$name.$domain"] 4990 d writelog "addalias" "add alias $name.$domain/[u viewname $idview] -> $nameref.$domainref" 4991 4992 return "" 4993} 4994 4995# 4996# Delete a host or an alias 4997# 4998# Input: 4999# - parameters: 5000# - dbfd: database handle 5001# - trr: RR of name to remove 5002# - idview: view id 5003# Output: 5004# - return value: empty string, or error message 5005# 5006# Note: we assume that an SQL transaction is already started 5007# by the calling procedure. No abort is done in this procedure. 5008# 5009# History 5010# 2013/03/28 : pda/jean : shared code between www/cgi/ and utils/ 5011# 5012 5013proc del-host {dbfd _trr idview} { 5014 upvar $_trr trr 5015 5016 set fqdn "$trr(name).$trr(domain)" 5017 set vn [u viewname $idview] 5018 5019 set cname [rr-cname-by-view trr $idview] 5020 if {$cname ne ""} then { 5021 set msg [del-alias-by-id $dbfd $trr(idrr)] 5022 if {$msg ne ""} then { 5023 return $msg 5024 } 5025 5026 set p "?" 5027 if {[read-rr-by-id $dbfd $cname tc]} then { 5028 set p "$tc(name).$tc(domain)" 5029 } 5030 d writelog "delalias" "delete alias $fqdn/$vn -> $p" 5031 } else { 5032 # 5033 # This is not an alias: delete all RR dependancies: 5034 # - aliases pointing this object 5035 # - MX 5036 # - IP addresses 5037 # 5038 set msg [del-rr-and-dependancies $dbfd trr] 5039 if {$msg ne ""} then { 5040 return $msg 5041 } 5042 d writelog "delname" "delete all of $fqdn/$vn" 5043 } 5044 5045 return "" 5046} 5047 5048# 5049# Delete one IP address 5050# 5051# Input: 5052# - parameters: 5053# - dbfd: database handle 5054# - addr: IP address to remove 5055# - trr: RR in which this address is located 5056# - idview: view id 5057# - _delobj: will contain in return the deleted object 5058# Output: 5059# - return value: empty string, or error message 5060# - parameter delobj: an IP address or a name if the whole object has 5061# been removed 5062# 5063# Note: we assume that an SQL transaction is already started 5064# by the calling procedure. No abort is done in this procedure. 5065# 5066# History 5067# 2013/03/28 : pda/jean : shared code between www/cgi/ and utils/ 5068# 5069 5070proc del-ip {dbfd addr _trr idview _delobj} { 5071 upvar $_trr trr 5072 upvar $_delobj delobj 5073 5074 set fqdn "$trr(name).$trr(domain)" 5075 set vn [u viewname $idview] 5076 5077 set lip [rr-ip-by-view trr $idview] 5078 if {[llength $lip] > 1} then { 5079 # 5080 # Only delete one of these addresses 5081 # 5082 5083 set sql "DELETE FROM dns.rr_ip i 5084 USING dns.rr r 5085 WHERE r.idrr = i.idrr 5086 AND i.addr = '$addr' 5087 AND r.idview = $idview" 5088 if {! [::pgsql::execsql $dbfd $sql msg]} then { 5089 return $msg 5090 } 5091 5092 set msg [touch-rr $dbfd $trr(idrr)] 5093 if {$msg ne ""} then { 5094 return $msg 5095 } 5096 5097 d writelog "deladdr" "delete address $addr from $fqdn/$vn" 5098 set delobj $addr 5099 } else { 5100 # 5101 # Delete the whole object 5102 # 5103 5104 set msg [del-rr-and-dependancies $dbfd trr] 5105 if {$msg ne ""} then { 5106 return $msg 5107 } 5108 d writelog "deladdr" "delete address $addr -> delete all $fqdn/$vn" 5109 set delobj $fqdn 5110 } 5111 5112 return "" 5113} 5114 5115# 5116# Update references to a RR when a new RR is created after a host renaming 5117# 5118# Input: 5119# - parameters: 5120# - dbfd : database handle 5121# - oidrr: id of old RR 5122# - nidrr: id of new RR 5123# - idview: view id 5124# Output: 5125# - return value: empty string, or error message 5126# 5127# History 5128# 2012/12/07 : pda/jean : design 5129# 5130 5131proc update-host-refs {dbfd oidrr nidrr} { 5132 set sql {} 5133 lappend sql "UPDATE dns.mail_role 5134 SET mboxhost = $nidrr 5135 WHERE mboxhost = $oidrr" 5136 lappend sql "UPDATE dns.rr_ip 5137 SET idrr = $nidrr 5138 WHERE idrr = $oidrr" 5139 lappend sql "UPDATE dns.rr_cname 5140 SET cname = $nidrr 5141 WHERE cname = $oidrr" 5142 lappend sql "UPDATE dns.rr_mx 5143 SET mx = $nidrr 5144 WHERE mx = $oidrr" 5145 lappend sql "UPDATE dns.relay_dom 5146 SET mx = $nidrr 5147 WHERE mx = $oidrr" 5148 set sql [join $sql ";"] 5149 if {[::pgsql::execsql $dbfd $sql msg]} then { 5150 set msg "" 5151 } 5152 return $msg 5153} 5154 5155# 5156# Update date and user id when a RR is modified 5157# 5158# Input: 5159# - parameters: 5160# - dbfd : database handle 5161# - idrr : RR id 5162# Output: 5163# - return value: empty string or error message 5164# 5165# History 5166# 2002/05/03 : pda/jean : design 5167# 2004/10/05 : pda : change date format 5168# 2010/11/13 : pda : use effective uid 5169# 2010/11/29 : pda : i18n 5170# 5171 5172proc touch-rr {dbfd idrr} { 5173 set date [clock format [clock seconds]] 5174 set idcor [lindex [d euid] 1] 5175 set sql "UPDATE dns.rr SET idcor = $idcor, date = '$date' WHERE idrr = $idrr" 5176 set msg "" 5177 if {! [::pgsql::execsql $dbfd $sql msg]} then { 5178 set msg [mc "RR update impossible: %s" $msg] 5179 } 5180 return $msg 5181} 5182 5183# 5184# Get group ids of all allowed groups for a list of IP addresses 5185# 5186# Input: 5187# - parameters: 5188# - dbfd : database handle 5189# - laddr : IP addresses to test 5190# Output: 5191# - return value: list of group ids 5192# 5193# History 5194# 2013/02/27 : pda/jean : design 5195# 5196 5197proc allowed-groups {dbfd laddr} { 5198 array set algrp {} 5199 5200 foreach addr $laddr { 5201 # 5202 # Look for groups which have access to this IP address. 5203 # 5204 5205 set sql "SELECT g.idgrp 5206 FROM global.nmgroup g, dns.p_network p, dns.network n 5207 WHERE g.idgrp = p.idgrp 5208 AND p.idnet = n.idnet 5209 AND ('$addr' <<= n.addr4 OR '$addr' <<= n.addr6) 5210 " 5211 set lidgrp {} 5212 pg_select $dbfd $sql tab { 5213 lappend lidgrp $tab(idgrp) 5214 } 5215 5216 # 5217 # Among selected groups, search for those who have access to 5218 # this host (checking all other permissions). 5219 # 5220 5221 foreach idgrp $lidgrp { 5222 set sql "SELECT dns.check_ip_grp ('$addr', $idgrp) AS ok" 5223 pg_select $dbfd $sql tab { 5224 if {$tab(ok) eq "t"} then { 5225 set algrp($idgrp) {} 5226 } 5227 } 5228 } 5229 } 5230 5231 return [array names algrp] 5232} 5233 5234# 5235# Display a RR with HTML 5236# 5237# Input: 5238# - parameters: 5239# - dbfd : database handle 5240# - idrr : RR id to search for, or -1 if _trr is already initialized 5241# - _trr : empty array, or initialized array (id idrr=-1) 5242# - idview : view id, or empty string to get all views 5243# - rrtmpl: URL template for some fields (see below) 5244# Output: 5245# - return value: empty string or error message 5246# - parameter _trr : see read-rr-by-id 5247# - global variables : 5248# - libconf(tabmachine) : array specification 5249# 5250# Note: 5251# - rrtmpl is a string ready for "array set" which has the following 5252# structure {key tmpl key tmpl ...} 5253# where key is one of: 5254# ip 5255# allowed-groups 5256# <may be more in the future> 5257# and tmpl has the following format: 5258# {url {formkey formval} {formkey formval} 5259# where url is the script name or any url (http://another.host/a/path) 5260# and formkey/formval are CGI parameters, where formval is formatted 5261# with value depending upon key: 5262# ip: %1$s <- ip, %2$s <- idview 5263# allowed-groups: %1$s <- groupname, %2$s <- "" 5264# 5265# History 5266# 2008/07/25 : pda/jean : design 5267# 2010/10/31 : pda : add ttl 5268# 2010/11/29 : pda : i18n 5269# 2012/10/31 : pda/jean : add views 5270# 2012/11/20 : pda/jean : add view filter to display a single view 5271# 2013/03/06 : pda/jean : add rrtmpl 5272# 5273 5274proc display-rr {dbfd idrr _trr idview rrtmpl} { 5275 global libconf 5276 upvar $_trr trr 5277 5278 # 5279 # Read RR if needed 5280 # 5281 5282 if {$idrr != -1 && [read-rr-by-id $dbfd $idrr trr] == -1} then { 5283 return "" 5284 } 5285 5286 # 5287 # Display all fields 5288 # 5289 5290 set lines {} 5291 5292 # 5293 # Special case if it is a CNAME in the view 5294 # 5295 5296 if {$idview ne ""} then { 5297 set cname [rr-cname-by-view trr $idview] 5298 if {$cname ne ""} then { 5299 set fqdn "$trr(name).$trr(domain)" 5300 if {! [read-rr-by-id $dbfd $cname tc]} then { 5301 return [mc {Cannot read host-id %s} $idalias] 5302 } 5303 5304 set fqdn2 "$tc(name).$tc(domain)" 5305 lappend lines [list Normal [mc "Alias name"] $fqdn] 5306 lappend lines [list Normal [mc "Points to"] $fqdn2] 5307 } 5308 } 5309 5310 # 5311 # Standard case 5312 # 5313 5314 if {$lines eq ""} then { 5315 # name 5316 lappend lines [list Normal [mc "Name"] "$trr(name).$trr(domain)"] 5317 5318 # IP address(es) 5319 set lip [rr-ip-by-view trr $idview] 5320 set nip [llength $lip] 5321 if {$nip <= 1} then { 5322 set at [mc "IP address"] 5323 } else { 5324 set at [mc "IP addresses"] 5325 } 5326 if {$nip == 0} then { 5327 set aa [mc "(none)"] 5328 } else { 5329 set aa {} 5330 foreach ip $lip { 5331 lappend aa [get-rr-tmpl "ip" $rrtmpl $ip $ip $idview] 5332 } 5333 set aa [join $aa ", "] 5334 } 5335 lappend lines [list Normal $at $aa] 5336 5337 # MAC address 5338 lappend lines [list Normal [mc "MAC address"] $trr(mac)] 5339 5340 # DHCP profile 5341 lappend lines [list Normal [mc "DHCP profile"] $trr(dhcpprof)] 5342 5343 # Machine type 5344 lappend lines [list Normal [mc "Type"] $trr(hinfo)] 5345 5346 # Right to emit with non auth SMTP : display only if it is used 5347 # (i.e. if there is at least one group wich owns this right) 5348 set sql "SELECT COUNT(*) AS nsmtp FROM global.nmgroup WHERE p_smtp = 1" 5349 set nsmtp 0 5350 pg_select $dbfd $sql tab { 5351 set nsmtp $tab(nsmtp) 5352 } 5353 if {$nsmtp > 0} then { 5354 if {$trr(sendsmtp)} then { 5355 set sendsmtp [mc "Yes"] 5356 } else { 5357 set sendsmtp [mc "No"] 5358 } 5359 lappend lines [list Normal [mc "SMTP emit right"] $sendsmtp] 5360 } 5361 5362 # TTL : display only if it used 5363 # (i.e. if there is at least one group wich owns this right and there 5364 # is a value) 5365 set sql "SELECT COUNT(*) AS nttl FROM global.nmgroup WHERE p_ttl = 1" 5366 set nttl 0 5367 pg_select $dbfd $sql tab { 5368 set nttl $tab(nttl) 5369 } 5370 if {$nttl > 0} then { 5371 set ttl $trr(ttl) 5372 if {$ttl != -1} then { 5373 lappend lines [list Normal [mc "TTL"] $ttl] 5374 } 5375 } 5376 5377 # comment 5378 lappend lines [list Normal [mc "Comment"] $trr(comment)] 5379 5380 # responsible (name) 5381 lappend lines [list Normal [mc "Responsible (name)"] $trr(respname)] 5382 5383 # responsible (mail) 5384 lappend lines [list Normal [mc "Responsible (mail)"] $trr(respmail)] 5385 5386 # aliases 5387 set la {} 5388 if {$idview eq ""} then { 5389 foreach va $trr(aliases) { 5390 lassign $va idview idalias 5391 if {[read-rr-by-id $dbfd $idalias ta]} then { 5392 lappend la "$ta(name).$ta(domain) ([u viewname $idview])" 5393 } 5394 } 5395 } else { 5396 foreach idalias [rr-aliases-by-view trr $idview] { 5397 if {[read-rr-by-id $dbfd $idalias ta]} then { 5398 lappend la "$ta(name).$ta(domain)" 5399 } 5400 } 5401 } 5402 if {[llength $la] > 0} then { 5403 lappend lines [list Normal [mc "Aliases"] [join $la " "]] 5404 } 5405 5406 # mail addresses recognized by this host 5407 set la {} 5408 foreach i [rr-mailaddr-by-view trr $idview] { 5409 lassign $i idmailaddr idviewa 5410 if {[read-rr-by-id $dbfd $idmailaddr ta]} then { 5411 lappend la "$ta(name).$ta(domain)/[u viewname $idviewa]" 5412 } 5413 } 5414 if {[llength $la] > 0} then { 5415 lappend lines [list Normal [mc "Mail addresses"] [join $la " "]] 5416 } 5417 5418 # 5419 # Allowed groups 5420 # 5421 5422 set lidgrp [allowed-groups $dbfd $lip] 5423 set lg {} 5424 foreach idgrp $lidgrp { 5425 set g [u groupname $idgrp] 5426 lappend lg [get-rr-tmpl "allowed-groups" $rrtmpl $g $g ""] 5427 } 5428 set lg [lsort $lg] 5429 lappend lines [list Normal [mc "Allowed groups"] [join $lg " "]] 5430 } 5431 5432 set html [::arrgen::output "html" $libconf(tabmachine) $lines] 5433 return $html 5434} 5435 5436proc get-rr-tmpl {key rrtmpl text arg1 arg2} { 5437 array set tmpl $rrtmpl 5438 5439 set text [::webapp::html-string $text] 5440 if {[info exists tmpl($key)]} then { 5441 set uarg [lreplace $tmpl($key) 0 0] 5442 set uarg [format $uarg $arg1 $arg2] 5443 d urlset "" [lindex $tmpl($key) 0] $uarg 5444 set url [d urlget ""] 5445 set link [::webapp::helem "a" $text "href" $url] 5446 } else { 5447 set link $text 5448 } 5449 return $link 5450} 5451 5452# 5453# Generates HTML code for a host description initially invisible 5454# and a link to toggle its visibility. 5455# 5456# Input: 5457# - parameters: 5458# - dbfd: database handle 5459# - _trr: initialized array (see read-rr-by-id) 5460# - idview: view id in which this host must be shown 5461# - rrtmpl: URL template for some fields (see display-rr) 5462# Output: 5463# - return value: list {<link> <desc>} where: 5464# - link is the HTML code for the link to the host name 5465# - desc is the HTML code for the host information display 5466# 5467# Note: this function needs an "invdisp" Javascript function in the 5468# HTML page 5469# 5470# History 5471# 2012/11/20 : pda/jean : design 5472# 2012/11/29 : pda/jean : move to a library function 5473# 2013/03/06 : pda/jean : add rrtmpl 5474# 5475 5476proc display-rr-masked {dbfd _trr idview rrtmpl} { 5477 upvar $_trr trr 5478 5479 h mask-next 5480 set link [h mask-link "$trr(name).$trr(domain)"] 5481 set desc [h mask-text [display-rr $dbfd -1 trr $idview $rrtmpl]] 5482 return [list $link $desc] 5483} 5484 5485############################################################################## 5486# Read domains 5487############################################################################## 5488 5489# 5490# Read all domains from database 5491# 5492# Input: 5493# - parameters: 5494# - dbfd: database handle 5495# - _tabdom: array to fill with domain names 5496# - _tabid: array to fill with domain ids 5497# Output: 5498# - parameter _tabdom: tabdom(<domainname>) <id> 5499# - parameter _tabid: tabdom(<id>) <domainname> 5500# 5501# History 5502# 2011/03/20 : pda : place in library 5503# 5504 5505proc read-all-domains {dbfd _tabdom _tabid} { 5506 upvar $_tabdom tabdom 5507 upvar $_tabid tabid 5508 5509 set sql "SELECT name, iddom FROM dns.domain" 5510 pg_select $dbfd $sql tab { 5511 set tabdom($tab(name)) $tab(iddom) 5512 set tabid($tab(iddom)) $tab(name) 5513 } 5514} 5515 5516############################################################################## 5517# Syntax check 5518############################################################################## 5519 5520# 5521# Check FQDN syntax according to RFC 1035. 5522# 5523# Input: 5524# - parameters: 5525# - dbfd : database handle 5526# - fqdn : name to test 5527# - _name : host name in return 5528# - _domain : host domain in return 5529# - _iddom : domain id in return (leave empty to not check domain existence) 5530# Output: 5531# - return value: empty string or error message 5532# - parameter _name : host name found 5533# - parameter _domain : host domain found 5534# - parameter _iddom : domain id found, or -1 if error 5535# 5536# History 5537# 2004/09/21 : pda/jean : design 5538# 2004/09/29 : pda/jean : add _domain parameter 5539# 2010/11/29 : pda : i18n 5540# 2011/02/18 : pda : iddom is optional 5541# 5542 5543proc check-fqdn-syntax {dbfd fqdn _name _domain {_iddom {}}} { 5544 upvar $_name name 5545 upvar $_domain domain 5546 5547 if {! [regexp {^([^\.]+)\.(.*)$} $fqdn bidon name domain]} then { 5548 return [mc "Invalid FQDN '%s'" $fqdn] 5549 } 5550 5551 set msg [check-name-syntax $name] 5552 if {$msg ne ""} then { 5553 return $msg 5554 } 5555 5556 if {$_iddom ne ""} then { 5557 upvar $_iddom iddom 5558 5559 set iddom [read-domain $dbfd $domain] 5560 if {$iddom < 0} then { 5561 return [mc "Invalid domain '%s'" $domain] 5562 } 5563 } 5564 5565 return "" 5566} 5567 5568# 5569# Check host name syntax (first part of a FQDN) according to RFC 1035 5570# 5571# Input: 5572# - parameters: 5573# - name : name to test 5574# Output: 5575# - return value: empty string or error message 5576# 5577# History 5578# 2002/04/11 : pda/jean : design 5579# 2010/11/29 : pda : i18n 5580# 5581 5582proc check-name-syntax {name} { 5583 # general case: a letter-or-digit at the beginning, a letter-or-digit 5584 # at the end (minus forbidden at the end) and letter-or-digit-or-minus 5585 # between. 5586 set re1 {[a-zA-Z0-9][-a-zA-Z0-9]*[a-zA-Z0-9]} 5587 # particular case: only one letter 5588 set re2 {[a-zA-Z0-9]} 5589 5590 if {[regexp "^$re1$" $name] || [regexp "^$re2$" $name]} then { 5591 set msg "" 5592 } else { 5593 set msg [mc "Invalid name '%s'" $name] 5594 } 5595 5596 return $msg 5597} 5598 5599# 5600# Check IP address (IPv4 or IPv6) syntax 5601# 5602# Input: 5603# - parameters: 5604# - dbfd : database handle 5605# - addr : address to test 5606# - type : "inet", "cidr", "loosecidr", "macaddr", "inet4", "cidr4" 5607# Output: 5608# - return value: empty string or error message 5609# 5610# Note : 5611# - type "cidr" is strict, "host" bits must be 0 (i.e.: "1.1.1.0/24" 5612# is valid, but not "1.1.1.1/24") 5613# - type "loosecidr" accepts "host" bits set to 1 5614# 5615# History 5616# 2002/04/11 : pda/jean : design 5617# 2002/05/06 : pda/jean : add type cidr 5618# 2002/05/23 : pda/jean : accept simplified cidr (a.b/x) 5619# 2004/01/09 : pda/jean : add IPv6 et radical simplification 5620# 2004/10/08 : pda/jean : add inet4 5621# 2004/10/20 : jean : forbid / for anything else than cidr type 5622# 2008/07/22 : pda : add type loosecidr (accepts /) 5623# 2010/10/07 : pda : add type cidr4 5624# 5625 5626proc check-ip-syntax {dbfd addr type} { 5627 5628 switch $type { 5629 inet4 { 5630 set cast "inet" 5631 set fam 4 5632 } 5633 cidr4 { 5634 set cast "cidr" 5635 set type "cidr" 5636 set fam 4 5637 } 5638 loosecidr { 5639 set cast "inet" 5640 set fam "" 5641 } 5642 default { 5643 set cast $type 5644 set fam "" 5645 set msg "?" 5646 } 5647 } 5648 set addr [::pgsql::quote $addr] 5649 set sql "SELECT $cast\('$addr'\) ;" 5650 set r "" 5651 if {[::pgsql::execsql $dbfd $sql msg]} then { 5652 if {$fam ne ""} then { 5653 pg_select $dbfd "SELECT family ('$addr') AS fam" tab { 5654 if {$tab(fam) != $fam} then { 5655 set r [mc {'%1$s' is not a valid IPv%2$s address} $addr $fam] 5656 } 5657 } 5658 } 5659 if {! ($type eq "cidr" || $type eq "loosecidr")} then { 5660 if {[regexp {/} $addr ]} then { 5661 set r [mc "The '/' character is not valid in the address '%s'" $addr] 5662 } 5663 } 5664 } else { 5665 if {$type eq "macaddr"} then { 5666 set r [mc "Invalid syntax for MAC address '%s'" $addr] 5667 } else { 5668 set r [mc "Invalid syntax for IP address '%s'" $addr] 5669 } 5670 } 5671 return $r 5672} 5673 5674# 5675# Check MAC address syntax 5676# 5677# Input: 5678# - parameters: 5679# - addr : address to test 5680# Output: 5681# - return value: empty string or error message 5682# 5683# History 5684# 2004/08/04 : pda/jean : design 5685# 2010/11/29 : pda : i18n 5686# 5687 5688proc check-mac-syntax {dbfd mac} { 5689 return [check-ip-syntax $dbfd $mac "macaddr"] 5690} 5691 5692# 5693# Check a DHCP profile id 5694# 5695# Input: 5696# - parameters: 5697# - dbfd : database handle 5698# - iddhcpprof : id of DHCP profile, or 0 5699# - _dhcpprof : variable contenant en retour le nom du profil 5700# - _msgvar : in return : error message 5701# Output: 5702# - return value: 1 if ok, 0 if error 5703# - _dhcpprof : name of found profile (or "No profile") 5704# - _msg : error message, if any 5705# 5706# History 5707# 2005/04/08 : pda/jean : design 5708# 2010/11/29 : pda : i18n 5709# 5710 5711proc check-iddhcpprof {dbfd iddhcpprof _dhcpprof _msg} { 5712 upvar $_dhcpprof dhcpprof 5713 upvar $_msg msg 5714 5715 set msg "" 5716 5717 if {! [regexp -- {^[0-9]+$} $iddhcpprof]} then { 5718 set msg [mc "Invalid syntax '%s' for DHCP profile" $iddhcpprof] 5719 } else { 5720 if {$iddhcpprof != 0} then { 5721 set sql "SELECT name FROM dns.dhcpprofile 5722 WHERE iddhcpprof = $iddhcpprof" 5723 set msg [mc "Invalid DHCP profile '%s'" $iddhcpprof] 5724 pg_select $dbfd $sql tab { 5725 set dhcpprof $tab(name) 5726 set msg "" 5727 } 5728 } else { 5729 set dhcpprof [mc "No profile"] 5730 } 5731 } 5732 5733 return [string equal $msg ""] 5734} 5735 5736############################################################################## 5737# View validation 5738############################################################################## 5739 5740# 5741# Checks if the selected views are authorized for this user 5742# 5743# Input: 5744# - parameters: 5745# - views : list of view ids given by the user 5746# Output: 5747# - return value: empty string or error message 5748# 5749# History 5750# 2012/10/30 : pda/jean : design 5751# 2012/10/31 : pda/jean : use nmuser class 5752# 5753 5754proc check-views {views} { 5755 set msg "" 5756 5757 if {[llength $views] == 0} then { 5758 set msg [mc "You must select at least one view"] 5759 5760 } else { 5761 # 5762 # Check authorized views 5763 # 5764 5765 set bad {} 5766 foreach id $views { 5767 if {! [u isallowedview $id]} then { 5768 set name [u viewname $id] 5769 if {$name eq ""} then { 5770 set name $id 5771 } 5772 lappend bad $name 5773 } 5774 } 5775 5776 if {[llength $bad]> 0} then { 5777 set bad [join $bad ", "] 5778 set msg [mc "You don't have access to these views: %s" $bad] 5779 } 5780 } 5781 5782 return $msg 5783} 5784 5785# 5786# Filter given view ids for host/address deletion/modification 5787# 5788# Input: 5789# - dbfd: database handle 5790# - _tabuid: user characteristics 5791# - mode: type of object ("host", "host-or-alias", "addr" or "mailrole") 5792# to delete/modify 5793# - object: FQDN or IP address 5794# - idviews: list of idviews specified by user, may be empty for all views 5795# - _chkv: contains, in return, parameters of filtered views 5796# Output: 5797# - return value: empty string or error message 5798# - array chkv: 5799# chkv(<idview>) = {<viewname> <errmsg or ""> <trr-ready-for-array-set>} 5800# chkv(idviews) = list of checked view ids 5801# chkv(ok) = list of view ids ok 5802# chkv(err) = list of view ids in error 5803# 5804# Note: 5805# - "host" and "addr" modes are for host edition 5806# object may be a fqdn or an IP address 5807# - "host-or-alias" and "addr" modes are for host deletion 5808# object may be a fqdn or an IP address 5809# - "mailrole" mode is for mail role edition 5810# object must be a fqdn 5811# 5812# History 5813# 2012/11/14 : pda/jean : design 5814# 2012/11/29 : pda/jean : isolate as a library function 5815# 2012/12/07 : pda/jean : generalization 5816# 2013/03/13 : pda/jean : distinguish alias case 5817# 5818 5819proc filter-views {dbfd _tabuid mode object idviews _chkv} { 5820 upvar $_tabuid tabuid 5821 upvar $_chkv chkv 5822 5823 set chkv(ok) {} 5824 set chkv(err) {} 5825 5826 # 5827 # Are views selected? 5828 # 5829 5830 set nviews [llength $idviews] 5831 if {$nviews == 0} then { 5832 # 5833 # No view selected by user. We must check all our views 5834 # in order to search deletion/modification candidates. 5835 # 5836 set myviewids [u myviewids] 5837 if {[llength $myviewids] == 0} then { 5838 return [mc "Sorry, but you do not have access to any view"] 5839 } 5840 } else { 5841 # 5842 # User has selected one or more views. This is a confirmation. 5843 # 5844 set myviewids $idviews 5845 set msg [check-views $myviewids] 5846 if {$msg ne ""} then { 5847 return $msg 5848 } 5849 } 5850 5851 # 5852 # Split FQDN into name and domain 5853 # 5854 if {$mode in {host host-or-alias mailrole}} then { 5855 set msg [check-fqdn-syntax $dbfd $object name domain] 5856 if {$msg ne ""} then { 5857 return $msg 5858 } 5859 } 5860 5861 # 5862 # Check object in all views 5863 # 5864 5865 set nok 0 5866 set nerr 0 5867 set mvi {} 5868 foreach idview $myviewids { 5869 set vn [u viewname $idview] 5870 5871 set found 0 5872 set err 0 5873 5874 catch {unset trr} 5875 5876 switch $mode { 5877 host - 5878 host-or-alias { 5879 set found 1 5880 set msg [check-authorized-host $dbfd $tabuid(idcor) $name $domain $idview trr "del-name"] 5881 5882 if {[info exists trr(idrr)] && $trr(idrr) eq ""} then { 5883 # 5884 # Name does not exist in this view 5885 # 5886 set found 0 5887 } elseif {$msg ne ""} then { 5888 set err 1 5889 } else { 5890 # 5891 # Is it an alias in this view? 5892 # 5893 5894 set cname [rr-cname-by-view trr $idview] 5895 set ip [rr-ip-by-view trr $idview] 5896 if {$mode eq "host-or-alias"} then { 5897 # 5898 # mode == "host-or-alias" 5899 # If it is not an alias, there must be at 5900 # least an IP address 5901 # 5902 if {$cname eq "" && $ip eq ""} then { 5903 set msg [mc {Name '%1$s' is not a host in view '%2$s'} $object $vn] 5904 set err 1 5905 } 5906 } else { 5907 # 5908 # mode == "host" 5909 # It must not be an alias and it must have at 5910 # least an IP address 5911 # 5912 if {$cname ne ""} then { 5913 set msg [mc {Name '%1$s' is an alias in view '%2$s'} $object $vn] 5914 set err 1 5915 } elseif {$ip eq ""} then { 5916 set msg [mc {Name '%1$s' is not a host in view '%2$s'} $object $vn] 5917 set err 1 5918 } 5919 } 5920 } 5921 } 5922 addr { 5923 # 5924 # IP address. Check that this address exists and get 5925 # all stored informations 5926 # 5927 5928 if {[read-rr-by-ip $dbfd $object $idview trr]} then { 5929 # 5930 # Check access to this name 5931 # 5932 5933 set found 1 5934 set name $trr(name) 5935 set domain $trr(domain) 5936 set msg [check-authorized-host $dbfd $tabuid(idcor) $name $domain $idview bidon "del-name"] 5937 if {$msg ne ""} then { 5938 set err 1 5939 } 5940 } 5941 } 5942 mailrole { 5943 set found 1 5944 set msg [check-authorized-host $dbfd $tabuid(idcor) $name $domain $idview trr "del-mailaddr"] 5945 5946 if {$msg ne ""} then { 5947 set err 1 5948 } 5949 } 5950 default { 5951 return "Internal error: unknown mode '$mode'" 5952 } 5953 } 5954 5955 if {$found} then { 5956 if {$err} then { 5957 set chkv($idview) [list $vn $msg [array get trr]] 5958 lappend chkv(err) $idview 5959 incr nerr 5960 } else { 5961 set chkv($idview) [list $vn "" [array get trr]] 5962 lappend chkv(ok) $idview 5963 incr nok 5964 } 5965 lappend mvi $idview 5966 } 5967 } 5968 set myviewids $mvi 5969 5970 # 5971 # If asked for a name, check that name exists 5972 # 5973 5974 if {$mode in {host host-or-alias} && $nok + $nerr == 0} then { 5975 return [mc "Name '%s' does not exist" $object] 5976 } 5977 5978 if {$mode eq "addr" && $nok + $nerr == 0} then { 5979 return [mc "Address '%s' not found" $object] 5980 } 5981 5982 # 5983 # Check that : 5984 # - there is at least one view in which we can delete/modify a name 5985 # - there is no view in error, if some views are specified 5986 # 5987 5988 if {$nok == 0 || ($nviews && $nerr > 0)} then { 5989 set msg "" 5990 foreach idview $myviewids { 5991 lassign $chkv($idview) vn m t 5992 if {$m ne ""} then { 5993 append msg [mc {Error detected in view '%1$s': %2$s} $vn $m] 5994 append msg "\n" 5995 } 5996 } 5997 return $msg 5998 } 5999 6000 # 6001 # At this point, myviewids contains: 6002 # - all user's view ids (good and in error) if confirmation is needed 6003 # - only good view ids if user has already confirmed 6004 # Views which do not include the searched IP address are not in myviewids 6005 # 6006 6007 set chkv(idviews) $myviewids 6008 6009 return "" 6010} 6011 6012# 6013# HTML code for host/idview selection page 6014# 6015# Input: 6016# - _chkv: parameters of filtered views 6017# - next: script to call 6018# Output: 6019# - return value: HTML code ready to be inserted in page 6020# 6021# History 6022# 2012/12/19 : pda/jean : design 6023# 6024 6025proc html-select-view {_chkv next} { 6026 upvar $_chkv chkv 6027 6028 set idviews $chkv(idviews) 6029 6030 set html "" 6031 foreach idview $idviews { 6032 lassign $chkv($idview) vn msg t 6033 6034 if {$msg eq ""} then { 6035 array unset trr 6036 array set trr $t 6037 6038 set fqdn "$trr(name).$trr(domain)" 6039 6040 d urlset "" $next [list \ 6041 [list "action" "edit"] \ 6042 [list "name" $trr(name)] \ 6043 [list "domain" $trr(domain)] \ 6044 [list "idview" $idview] \ 6045 ] 6046 d urladdnext "" 6047 set url [d urlget ""] 6048 6049 set a [mc {<a href="%1$s">Modify '%2$s'</a> in view '%3$s'} $url $fqdn $vn] 6050 append html [::webapp::helem "li" $a] 6051 append html "\n" 6052 } 6053 } 6054 set html [::webapp::helem "ul" $html] 6055 6056 return $html 6057} 6058 6059############################################################################## 6060# Domain validation 6061############################################################################## 6062 6063# 6064# Search for a domain name in the database 6065# 6066# Input: 6067# - parameters: 6068# - dbfd : database handle 6069# - domain : domain to search (not terminated by a ".") 6070# Output: 6071# - return value: id of domain if found, -1 if not found 6072# 6073# History 6074# 2002/04/11 : pda/jean : design 6075# 2010/11/29 : pda : i18n 6076# 6077 6078proc read-domain {dbfd domain} { 6079 set domain [::pgsql::quote $domain] 6080 set iddom -1 6081 pg_select $dbfd "SELECT iddom FROM dns.domain WHERE name = '$domain'" tab { 6082 set iddom $tab(iddom) 6083 } 6084 return $iddom 6085} 6086 6087# 6088# Checks if the domain is authorized for this user 6089# 6090# Input: 6091# - parameters: 6092# - dbfd : database handle 6093# - idcor : user id 6094# - _iddom : domain id or -1 to read from domain 6095# - _domain : domain, or "" to read from iddom 6096# - roles : roles to test (column names in p_dom) 6097# Output: 6098# - return value: empty string or error message 6099# - parameters _iddom and _domain : fetched values 6100# 6101# History 6102# 2002/04/11 : pda/jean : design 6103# 2002/05/06 : pda/jean : use groups 6104# 2004/02/06 : pda/jean : add roles 6105# 2010/11/29 : pda : i18n 6106# 6107 6108proc check-domain {dbfd idcor _iddom _domain roles} { 6109 upvar $_iddom iddom 6110 upvar $_domain domain 6111 6112 set msg "" 6113 6114 # 6115 # Read domain if needed 6116 # 6117 if {$iddom == -1} then { 6118 set iddom [read-domain $dbfd $domain] 6119 if {$iddom == -1} then { 6120 set msg [mc "Domain '%s' not found" $domain] 6121 } 6122 } elseif {$domain eq ""} then { 6123 set sql "SELECT name FROM dns.domain WHERE iddom = $iddom" 6124 pg_select $dbfd $sql tab { 6125 set domain $tab(name) 6126 } 6127 if {$domain eq ""} then { 6128 set msg [mc "Domain-id '%s' not found" $iddom] 6129 } 6130 } 6131 6132 # 6133 # Check if we have rights on this domain 6134 # 6135 if {$msg eq ""} then { 6136 set where "" 6137 foreach r $roles { 6138 append where "AND p_dom.$r > 0 " 6139 } 6140 6141 set found 0 6142 set sql "SELECT p_dom.iddom FROM dns.p_dom, global.nmuser 6143 WHERE nmuser.idcor = $idcor 6144 AND nmuser.idgrp = p_dom.idgrp 6145 AND p_dom.iddom = $iddom 6146 $where 6147 " 6148 pg_select $dbfd $sql tab { 6149 set found 1 6150 } 6151 if {! $found} then { 6152 set msg [mc "You don't have rights on domain '%s'" $domain] 6153 } 6154 } 6155 6156 return $msg 6157} 6158 6159# 6160# Check if the IP address is authorized for this user 6161# 6162# Input: 6163# - parameters: 6164# - dbfd : database handle 6165# - idcor : user id 6166# - addr : IP address to test 6167# Output: 6168# - return value: 1 if ok, 0 if error 6169# 6170# History 6171# 2002/04/11 : pda/jean : design 6172# 2002/05/06 : pda/jean : use groups 6173# 2004/01/14 : pda/jean : add IPv6 6174# 2010/11/29 : pda : i18n 6175# 6176 6177proc check-authorized-ip {dbfd idcor addr} { 6178 set r 0 6179 set sql "SELECT dns.check_ip_cor ('$addr', $idcor) AS ok" 6180 pg_select $dbfd $sql tab { 6181 set r [string equal $tab(ok) "t"] 6182 } 6183 return $r 6184} 6185 6186# 6187# Check if the user has adequate rights to a machine, by checking 6188# that he own all IP addresses 6189# 6190# Input: 6191# - parameters: 6192# - dbfd : database handle 6193# - idcor : user id 6194# - idrr : RR id to search for, or -1 if _trr is already initialized 6195# - _trr : see read-rr-by-name 6196# Output: 6197# - return value: 1 if ok, 0 if error 6198# 6199# History 6200# 2002/04/19 : pda/jean : design 6201# 2010/11/29 : pda : i18n 6202# 2012/10/30 : pda/jean : add views 6203# 6204 6205proc check-name-by-addresses {dbfd idcor idrr _trr} { 6206 upvar $_trr trr 6207 6208 set ok 1 6209 6210 # 6211 # Read RR if needed 6212 # 6213 6214 if {$idrr != -1 && [read-rr-by-id $dbfd $idrr trr] == -1} then { 6215 set trr(ip) {} 6216 set ok 1 6217 } 6218 6219 # 6220 # Check all addresses and views 6221 # 6222 6223 if {[info exists trr(ip)]} then { 6224 foreach viewip $trr(ip) { 6225 lassign $viewip idview ip 6226 if {! [u isallowedview $idview]} then { 6227 set ok 0 6228 break 6229 } 6230 if {! [check-authorized-ip $dbfd $idcor $ip]} then { 6231 set ok 0 6232 break 6233 } 6234 } 6235 } 6236 6237 return $ok 6238} 6239 6240# 6241# Check if the user as the right to add/modify/delete a given name 6242# according to a given context. 6243# 6244# Input: 6245# - parameters: 6246# - dbfd : database handle 6247# - idcor : user id 6248# - name : name to test (first component of FQDN) 6249# - domain : domain to test (the n-1 last components of FQDN) 6250# - idview : view id in which this FQDN must be tested 6251# - trr : in return, information on the host (see read-rr-by-id) 6252# - context : the context to check 6253# Output: 6254# - return value: empty string or error message 6255# - parameter trr : contains informations on the RR found, or if the RR 6256# doesn't exist, trr(idrr) = "" and trr(iddom) = domain id 6257# 6258# Detail of tests: 6259# According to context: 6260# "host" 6261# check-domain (domain, idcor, "") and views 6262# if name.domain is ALIAS then error 6263# if name.domain is MX then error 6264# if name.domain is ADDRMAIL 6265# then check-all-IP-addresses (mail host, idcor) 6266# check-domain (domain, idcor, "") 6267# if name.domain has IP addresses 6268# then check-all-IP-addresses (machine, idcor) 6269# if no test is false, then OK 6270# "existing-host" 6271# identical to "host", but the name must have at least one IP address 6272# "del-name" 6273# check-domain (domain, idcor, "") and views 6274# if name.domain is ALIAS 6275# then check-all-IP-addresses (pointed host, idcor) 6276# if name.domain is MX then error 6277# if name.domain has IP addresses 6278# then check-all-IP-addresses (machine, idcor) 6279# if name.domain is ADDRMAIL 6280# then check-all-IP-addresses (mail host, idcor) 6281# check-domain (domain, idcor, "") 6282# if no test is false, then OK 6283# "alias" 6284# check-domain (domain, idcor, "") and views 6285# if name.domain is ALIAS then error 6286# if name.domain is MX then error 6287# if name.domain is target of a MX then error 6288# if name.domain is ADDRMAIL then error 6289# if name.domain has IP addresses then error 6290# if no test is false, then OK 6291# "mx" 6292# check-domain (domain, idcor, "") and views 6293# if name.domain is ALIAS then error 6294# if name.domain is MX 6295# then check-all-IP-addresses (mail exchangers, idcor) 6296# if name.domain is ADDRMAIL then error 6297# if no test is false, then OK 6298# "add-mailaddr" 6299# check-domain (domain, idcor, "mailrole") and views 6300# if name.domain is ALIAS then error 6301# if name.domain is MX then error 6302# if name.domain is ADDRMAIL then error 6303# if name.domain is MAILHOST then error 6304# if name.domain has IP addresses 6305# check-all-IP-addresses (name.domain, idcor) 6306# if no test is false, then OK 6307# "del-mailaddr" 6308# check-domain (domain, idcor, "mailrole") and views 6309# if name.domain is ALIAS then error 6310# if name.domain is MX then error 6311# if name.domain is target of a MX then error 6312# if name.domain is NOT ADDRMAIL then error 6313# if name.domain is ADDRMAIL 6314# check-all-IP-addresses (mail host, idcor) 6315# check-domain (domain, idcor, "") 6316# if name.domain has IP addresses 6317# check-all-IP-addresses (name.domain, idcor) 6318# if no test is false, then OK 6319# 6320# check-IP-addresses (host, idcor) 6321# if there is no address 6322# then error 6323# else check that all IP addresses are mine (with an AND) 6324# end if 6325# 6326# Bug: this procedure is never called with the "mx" parameter 6327# 6328# History 6329# 2004/02/27 : pda/jean : specification 6330# 2004/02/27 : pda/jean : coding 6331# 2004/03/01 : pda/jean : use trr(iddom) instead of iddom 6332# 2010/11/29 : pda : i18n 6333# 2012/10/30 : pda/jean : add views 6334# 2013/04/10 : pda/jean : accept only one view 6335# 6336 6337proc check-authorized-host {dbfd idcor name domain idview _trr context} { 6338 upvar $_trr trr 6339 6340 array set testrights { 6341 host { 6342 {domain {}} 6343 {alias REJECT} 6344 {mx REJECT} 6345 {ip CHECK} 6346 {mailaddr CHECK} 6347 } 6348 existing-host { 6349 {domain {}} 6350 {alias REJECT} 6351 {mx REJECT} 6352 {ip CHECK} 6353 {ip EXISTS} 6354 {mailaddr CHECK} 6355 } 6356 alias { 6357 {domain {}} 6358 {alias REJECT} 6359 {mx REJECT} 6360 {ip REJECT} 6361 {mailaddr REJECT} 6362 } 6363 del-name { 6364 {domain {}} 6365 {alias CHECK} 6366 {mx REJECT} 6367 {ip CHECK} 6368 {mailaddr CHECK} 6369 } 6370 mx { 6371 {domain {}} 6372 {alias REJECT} 6373 {mx CHECK} 6374 {ip CHECK} 6375 {mailaddr REJECT} 6376 } 6377 add-mailaddr { 6378 {domain mailrole} 6379 {alias REJECT} 6380 {mx REJECT} 6381 {mailaddr REJECT} 6382 {mailhost REJECT} 6383 {ip CHECK} 6384 } 6385 del-mailaddr { 6386 {domain mailrole} 6387 {alias REJECT} 6388 {mx REJECT} 6389 {mailaddr CHECK} 6390 {mailaddr EXISTS} 6391 {ip CHECK} 6392 } 6393 } 6394 6395 6396 # 6397 # Get the list of actions associated with the context 6398 # 6399 6400 if {! [info exists testrights($context)]} then { 6401 return [mc "Internal error: invalid context '%s'" $context] 6402 } 6403 6404 # 6405 # For each view, process tests in the given order, and break as 6406 # soon as a test fails 6407 # 6408 6409 set fqdn "$name.$domain" 6410 6411 foreach a $testrights($context) { 6412 set parm [lindex $a 1] 6413 switch [lindex $a 0] { 6414 domain { 6415 set msg [check-views [list $idview]] 6416 if {$msg ne ""} then { 6417 return $msg 6418 } 6419 set viewname [u viewname $idview] 6420 6421 set iddom -1 6422 set msg [check-domain $dbfd $idcor iddom domain $parm] 6423 if {$msg ne ""} then { 6424 return $msg 6425 } 6426 6427 if {! [read-rr-by-name $dbfd $name $iddom $idview trr]} then { 6428 set trr(idrr) "" 6429 set trr(iddom) $iddom 6430 } 6431 } 6432 alias { 6433 set idcname [rr-cname-by-view trr $idview] 6434 if {$idcname ne ""} then { 6435 read-rr-by-id $dbfd $idcname t 6436 set fqdnref "$t(name).$t(domain)" 6437 switch $parm { 6438 REJECT { 6439 return [mc {%1$s is an alias of host %2$s in view %3$s} $fqdn $fqdnref $viewname] 6440 } 6441 CHECK { 6442 set ok [check-name-by-addresses $dbfd $idcor -1 t] 6443 if {! $ok} then { 6444 return [mc {You don't have rights on some IP addresses of '%1$s' referenced by alias '%2$s'} $fqdnref $fqdn] 6445 } 6446 } 6447 default { 6448 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6449 } 6450 } 6451 } 6452 } 6453 mx { 6454 set lmx [rr-mx-by-view trr $idview] 6455 foreach mx $lmx { 6456 switch $parm { 6457 REJECT { 6458 return [mc "'%s' is a MX" $fqdn] 6459 } 6460 CHECK { 6461 set idrr [lindex $mx 1] 6462 set ok [check-name-by-addresses $dbfd $idcor $idrr t] 6463 if {! $ok} then { 6464 set fqdnmx "$t(name).$t(domain)" 6465 return [mc {You don't have rights on some IP addresses of '%1$s' referenced by MX '%2$s'} $fqdnmx $fqdn] 6466 } 6467 } 6468 default { 6469 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6470 } 6471 } 6472 } 6473 } 6474 mailaddr { 6475 # get mailbox host for this address 6476 set rm [rr-mailrole-by-view trr $idview] 6477 if {$rm ne ""} then { 6478 lassign $rm idrr idviewmbx 6479 # get mbox host 6480 if {! [read-rr-by-id $dbfd $idrr trrh]} then { 6481 return [mc "Internal error: id '%s' doesn't exists for a mail host" $idrr] 6482 } 6483 switch $parm { 6484 REJECT { 6485 # This name is already a mail address 6486 # (it already has a mailbox host) 6487 set fqdnm "$trrh(name).$trrh(domain)" 6488 return [mc {%1$s in view %2$s is a mail address hosted by %3$s in view %4$s} $fqdn $viewname $fqdnm [u viewname $idviewmbx]] 6489 } 6490 CHECK { 6491 6492 # IP address check 6493 set ok [check-name-by-addresses $dbfd $idcor -1 trrh] 6494 if {! $ok} then { 6495 return [mc "You don't have rights on host holding mail for '%s'" $fqdn] 6496 } 6497 6498 # Mail host checking 6499 set bidon -1 6500 set msg [check-domain $dbfd $idcor bidon trrh(domain) ""] 6501 if {$msg ne ""} then { 6502 set r [mc "You don't have rights on host holding mail for '%s'" $fqdn] 6503 append r "\n$msg" 6504 return $r 6505 } 6506 } 6507 EXISTS { 6508 # nothing 6509 } 6510 default { 6511 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6512 } 6513 } 6514 } else { 6515 # this address has no mailbox host, so it is 6516 # not a mail role 6517 switch $parm { 6518 REJECT - 6519 CHECK { 6520 # nothing 6521 } 6522 EXISTS { 6523 return [mc {'%1$s' is not a mail role in view '%2$s'} $fqdn $viewname] 6524 } 6525 default { 6526 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6527 } 6528 } 6529 } 6530 } 6531 mailhost { 6532 set laddr [rr-mailaddr-by-view trr $idview] 6533 switch $parm { 6534 REJECT { 6535 # remove the name (in all views) from the list 6536 # of mail domains hosted on this host 6537 while {[set pos [lsearch -exact -index 0 \ 6538 $laddr $trr(idrr)]] != -1} { 6539 set laddr [lreplace $laddr $pos $pos] 6540 } 6541 if {[llength $laddr] > 0} then { 6542 return [mc "'%s' is a mail host for mail domains" $fqdn] 6543 } 6544 } 6545 default { 6546 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6547 } 6548 } 6549 } 6550 ip { 6551 set lip [rr-ip-by-view trr $idview] 6552 switch $parm { 6553 REJECT { 6554 if {[llength $lip] > 0} then { 6555 return [mc {'%1$s' has IP addresses in view '%2$s'} $fqdn $viewname] 6556 } 6557 } 6558 EXISTS { 6559 if {[llength $lip] == 0} then { 6560 return [mc {Name '%1$s' is not a host in view '%2$s'} $fqdn $viewname] 6561 } 6562 } 6563 CHECK { 6564 set ok [check-name-by-addresses $dbfd $idcor -1 trr] 6565 if {! $ok} then { 6566 return [mc "You don't have rights on some IP addresses of '%s'" $fqdn] 6567 } 6568 } 6569 default { 6570 return [mc {Internal error: invalid parameter '%1$s' for '%2$s'} $parm "$context/$a"] 6571 } 6572 } 6573 } 6574 } 6575 } 6576 6577 return "" 6578} 6579 6580# 6581# Check MX informations (given form field values) 6582# 6583# Input: 6584# - parameters: 6585# - dbfd : database handle 6586# - prio : priority read from the form 6587# - name : MX name, read from the form 6588# - domain : MX domain name, read from the form 6589# - idview : view id 6590# - idcor : user id 6591# - _msg : error message 6592# Output: 6593# - return value: list {prio idmx} where 6594# - prio = numeric priority (int syntax ok) 6595# - idmx = existing RR id 6596# - parameters: 6597# - _msg : empty string or error message 6598# 6599# History 6600# 2003/04/25 : pda/jean : design 6601# 2004/03/04 : pda/jean : common procedure 6602# 2010/11/29 : pda : i18n 6603# 2013/03/20 : pda : add views 6604# 6605 6606proc check-mx-target {dbfd prio name domain idview idcor _msg} { 6607 upvar $_msg msg 6608 6609 # 6610 # Syntaxic checking of priority 6611 # 6612 6613 if {! [regexp {^[0-9]+$} $prio]} then { 6614 set msg [mc {Invalid MX priority '%1$s' for '%2$s'} $prio "$name.$domain"] 6615 return {} 6616 } 6617 6618 # 6619 # Check relay, domain, etc. 6620 # 6621 6622 set msg [check-authorized-host $dbfd $idcor $name $domain $idview trr "existing-host"] 6623 if {$msg ne ""} then { 6624 return $msg 6625 } 6626 6627 # 6628 # Build up the result 6629 # 6630 6631 return [list $prio $trr(idrr)] 6632} 6633 6634# 6635# Check MX 6636# 6637# Input: 6638# - parameters: 6639# - dbfd : database handle 6640# - name : MX name 6641# - _iddom : in return, domain id 6642# - domain : MX domain name 6643# - idview: view id 6644# - idcor : user id 6645# - _exists : 1 if RR exists, 0 if not 6646# - _trr : RR information read from database 6647# Output: 6648# - return value: empty string or error message 6649# - parameter _trr : RR information on return 6650# 6651# History 6652# 2010/12/09 : pda : isolate common code 6653# 2013/03/21 : pda : add views 6654# 6655 6656proc check-authorized-mx {dbfd idcor name _iddom domain idview _exists _trr} { 6657 upvar $_exists exists 6658 upvar $_iddom iddom 6659 upvar $_trr trr 6660 6661 # 6662 # Validate MX name and domain 6663 # 6664 6665 set msg [check-name-syntax $name] 6666 if {$msg ne ""} then { 6667 d error $msg 6668 } 6669 6670 set iddom -1 6671 set msg [check-domain $dbfd $idcor iddom domain ""] 6672 if {$msg ne ""} then { 6673 d error $msg 6674 } 6675 6676 # 6677 # Get information about this name if it already exists 6678 # 6679 6680 set exists [read-rr-by-name $dbfd $name $iddom $idview trr] 6681 if {$exists} then { 6682 # 6683 # If it already exists, check that it is not a CNAME 6684 # 6685 6686 set cname [rr-cname-by-view trr $idview] 6687 if {$cname ne ""} then { 6688 return [mc "'%s' is an alias" $name] 6689 } 6690 6691 # 6692 # MX exists, we must check that the user has permissions 6693 # to access all referenced domains. 6694 # 6695 6696 foreach lmx [rr-mx-by-view trr $idview] { 6697 lassign $lmx prio idmx 6698 if {! [read-rr-by-id $dbfd $idmx tabmx]} then { 6699 return [mc "Internal error: rr_mx table references RR '%s', not found in the rr table" $idmx] 6700 } 6701 set iddom $tabmx(iddom) 6702 set msg [check-domain $dbfd $idcor iddom tabmx(domain) ""] 6703 if {$msg ne ""} then { 6704 return [mc {MX '%1$s' points to a domain on which you don't have rights\n%2$s} "$tabmx(name).$tabmx(domain)" $msg] 6705 } 6706 } 6707 } 6708 6709 return "" 6710} 6711 6712# 6713# Check domains and mail relays 6714# 6715# Input: 6716# - parameters: 6717# - dbfd : database handle 6718# - idcor : user id 6719# - _iddom : in return, id of found domain 6720# - domain : the domain to search 6721# - idview : view id 6722# Output: 6723# - return value: empty string or error message 6724# - parameter iddom : id of found domain, or -1 if error 6725# 6726# History 6727# 2004/03/04 : pda/jean : design 6728# 2010/11/29 : pda : i18n 6729# 2013/03/20 : pda : add views 6730# 6731 6732proc check-domain-relay {dbfd idcor _iddom domain idview} { 6733 upvar $_iddom iddom 6734 6735 # 6736 # Check the domain 6737 # 6738 6739 set msg [check-domain $dbfd $idcor iddom domain "mailrole"] 6740 if {$msg ne ""} then { 6741 return $msg 6742 } 6743 6744 # 6745 # Check that we own all specified relays 6746 # 6747 6748 set sql "SELECT r.name AS name, d.name AS domain 6749 FROM dns.relay_dom rd, dns.rr r, dns.domain d 6750 WHERE rd.iddom = $iddom 6751 AND rd.mx = r.idrr 6752 AND r.iddom = d.iddom 6753 AND r.idview = $idview 6754 " 6755 pg_select $dbfd $sql tab { 6756 set msg [check-authorized-host $dbfd $idcor $tab(name) $tab(domain) $idview trr "existing-host"] 6757 if {$msg ne ""} then { 6758 return [mc {You don't have rights to some relays of domain '%1$s': %2$s} $domain $msg] 6759 } 6760 } 6761 6762 return "" 6763} 6764 6765# 6766# Check MAC against syntax errors and DHCP ranges 6767# 6768# Input: 6769# - parameters: 6770# - dbfd: database handle 6771# - mac: MAC address (empty or not empty) 6772# - trr: trr of host for which this MAC address is 6773# - idview: view id 6774# Output: 6775# - return value: empty string or error message 6776# 6777# History 6778# 2013/04/05 : pda/jean : design 6779# 6780 6781proc check-mac {dbfd mac _trr idview} { 6782 upvar $_trr trr 6783 6784 set msg "" 6785 if {$mac ne ""} then { 6786 set msg [check-mac-syntax $dbfd $mac] 6787 if {$msg eq ""} then { 6788 set msg [check-static-dhcp $dbfd $mac [rr-ip-by-view trr $idview]] 6789 } 6790 } 6791 return $msg 6792} 6793 6794# 6795# Check that no static DHCP association (IP address with an associate 6796# non null MAC address) is within a DHCP range 6797# 6798# Input: 6799# - parameters: 6800# - dbfd : database handle 6801# - mac : MAC address (empty or not empty) 6802# - lip : IP (IPv4 and IPv6) address list 6803# Output: 6804# - return value: empty string or error message 6805# 6806# History 6807# 2004/08/04 : pda/jean : design 6808# 2010/11/29 : pda : i18n 6809# 6810 6811proc check-static-dhcp {dbfd mac lip} { 6812 set r "" 6813 if {$mac ne ""} then { 6814 foreach ip $lip { 6815 set sql "SELECT min, max 6816 FROM dns.dhcprange 6817 WHERE '$ip' >= min AND '$ip' <= max" 6818 pg_select $dbfd $sql tab { 6819 set r [mc {Impossible to use MAC address '%1$s' because IP address '%2$s' is in DHCP dynamic range [%3$s..%4$s]} $mac $ip $tab(min) $tab(max)] 6820 } 6821 if {$r ne ""} then { 6822 break 6823 } 6824 } 6825 } 6826 return $r 6827} 6828 6829# 6830# Check possible values for a TTL (see RFC 2181) 6831# 6832# Input: 6833# - parameters: 6834# - ttl : value to check 6835# Output: 6836# - return value: empty string or error message 6837# 6838# History 6839# 2010/11/02 : pda/jean : design, from jean's code 6840# 2010/11/29 : pda : i18n 6841# 6842 6843proc check-ttl {ttl} { 6844 set r "" 6845 # 2^31-1 6846 set maxttl [expr 0x7fffffff] 6847 if {! [regexp {^\d+$} $ttl]} then { 6848 set r [mc "Invalid TTL: must be a positive integer"] 6849 } else { 6850 if {$ttl > $maxttl} then { 6851 set r [mc "Invalid TTL: must be less than %s" $maxttl] 6852 } 6853 } 6854 return $r 6855} 6856 6857############################################################################## 6858# User checking 6859############################################################################## 6860 6861# 6862# Check syntax of a group name 6863# 6864# Input: 6865# - parameters: 6866# - group : name of group 6867# Output: 6868# - return value: empty string or error message 6869# 6870# History 6871# 2008/02/13 : pda/jean : design 6872# 2010/11/29 : pda : i18n 6873# 6874 6875proc check-group-syntax {group} { 6876 if {[regexp {^[-A-Za-z0-9]*$} $group]} then { 6877 set r "" 6878 } else { 6879 set r [mc "Invalid group name '%s' (allowed chars: letters, digits and minus symbol)" $group] 6880 } 6881 return $r 6882} 6883 6884 6885############################################################################## 6886# Hinfo checking 6887############################################################################## 6888 6889# 6890# Returns HINFO index in the database 6891# 6892# Input: 6893# - dbfd : database handle 6894# - text : hinfo to search 6895# Output: 6896# - return value: index, or -1 if not found 6897# 6898# History 6899# 2002/05/03 : pda/jean : design 6900# 2010/11/29 : pda : i18n 6901# 6902 6903proc read-hinfo {dbfd text} { 6904 set qtext [::pgsql::quote $text] 6905 set idhinfo -1 6906 pg_select $dbfd "SELECT idhinfo FROM dns.hinfo WHERE name = '$qtext'" tab { 6907 set idhinfo $tab(idhinfo) 6908 } 6909 return $idhinfo 6910} 6911 6912############################################################################## 6913# DHCP profile checking 6914############################################################################## 6915 6916# 6917# Returns DHCP profile index in the database 6918# 6919# Input: 6920# - dbfd : database handle 6921# - text : profile name to search, or "" 6922# Output: 6923# - return value: index, or -1 if not found 6924# 6925# History 6926# 2005/04/11 : pda/jean : design 6927# 2010/11/29 : pda : i18n 6928# 6929 6930proc read-dhcp-profile {dbfd text} { 6931 if {$text eq ""} then { 6932 set iddhcpprof 0 6933 } else { 6934 set qtext [::pgsql::quote $text] 6935 set sql "SELECT iddhcpprof FROM dns.dhcpprofile WHERE name = '$qtext'" 6936 set iddhcpprof -1 6937 pg_select $dbfd $sql tab { 6938 set iddhcpprof $tab(iddhcpprof) 6939 } 6940 } 6941 return $iddhcpprof 6942} 6943 6944############################################################################## 6945# Netmagis standard HTML menus 6946############################################################################## 6947 6948# 6949# Get a ready to use HTML menu to set HINFO values. 6950# 6951# Input: 6952# - dbfd : database handle 6953# - field : field name 6954# - defval : default hinfo (textual value) 6955# Output: 6956# - return value: ready to use HTML string 6957# 6958# History 6959# 2002/05/03 : pda/jean : design 6960# 2010/12/01 : pda : i18n 6961# 6962 6963proc menu-hinfo {dbfd field defval} { 6964 set lhinfo {} 6965 set sql "SELECT name FROM dns.hinfo 6966 WHERE present = 1 6967 ORDER BY sort ASC, name ASC" 6968 set i 0 6969 set defindex 0 6970 pg_select $dbfd $sql tab { 6971 lappend lhinfo [list $tab(name) $tab(name)] 6972 if {$tab(name) eq $defval} then { 6973 set defindex $i 6974 } 6975 incr i 6976 } 6977 return [::webapp::form-menu $field 1 0 $lhinfo [list $defindex]] 6978} 6979 6980# 6981# Get a ready to use HTML menu to set DHCP profile value, or a hidden 6982# field if the group do not have access to any DHCP Profile. 6983# 6984# Input: 6985# - dbfd : database handle 6986# - field : field name 6987# - idcor : user id 6988# - iddhcpprof : default selected profile, or 0 6989# Output: 6990# - return value: list with 2 HTML strings {title menu} 6991# 6992# History 6993# 2005/04/08 : pda/jean : design 6994# 2008/07/23 : pda/jean : change output format 6995# 2010/11/29 : pda : i18n 6996# 6997 6998proc menu-dhcp-profile {dbfd field idcor iddhcpprof} { 6999 # 7000 # Get all DHCP profiles for this group 7001 # 7002 7003 set sql "SELECT d.iddhcpprof, d.name 7004 FROM dns.p_dhcpprofile p, dns.dhcpprofile d, global.nmuser u 7005 WHERE u.idcor = $idcor 7006 AND p.idgrp = u.idgrp 7007 AND p.iddhcpprof = d.iddhcpprof 7008 ORDER BY p.sort ASC, d.name" 7009 set lprof {} 7010 set lsel {} 7011 set idx 1 7012 pg_select $dbfd $sql tab { 7013 lappend lprof [list $tab(iddhcpprof) $tab(name)] 7014 if {$tab(iddhcpprof) == $iddhcpprof} then { 7015 lappend lsel $idx 7016 } 7017 incr idx 7018 } 7019 7020 # 7021 # Is there at least one profile? 7022 # 7023 7024 if {[llength $lprof] > 0} then { 7025 # 7026 # Is the default selected profile in our list? 7027 # 7028 7029 if {$iddhcpprof != 0 && [llength $lsel] == 0} then { 7030 # 7031 # We must add it at the end of the list. 7032 # 7033 7034 set sql "SELECT iddhcpprof, name 7035 FROM dns.dhcpprofile 7036 WHERE iddhcpprof = $iddhcpprof" 7037 pg_select $dbfd $sql tab { 7038 lappend lprof [list $tab(iddhcpprof) $tab(name)] 7039 lappend lsel $idx 7040 } 7041 } 7042 7043 # 7044 # Special case at the beginning of the list 7045 # 7046 7047 set lprof [linsert $lprof 0 [list 0 [mc "No profile"]]] 7048 7049 set title [mc "DHCP profile"] 7050 set html [::webapp::form-menu $field 1 0 $lprof $lsel] 7051 7052 } else { 7053 # 7054 # No profile found. We hide the field. 7055 # 7056 7057 set title "" 7058 set html [::webapp::form-hidden $field $iddhcpprof] 7059 } 7060 7061 return [list $title $html] 7062} 7063 7064# 7065# Get an HTML button "SMTP emit right" for a host, or a hidden field 7066# if the group do not have the according right. 7067# 7068# Input: 7069# - dbfd : database handle 7070# - field : field name 7071# - _tabuid : user characteristics 7072# - sendsmtp : default selected value 7073# Output: 7074# - return value: list with 2 HTML strings {title menu} 7075# 7076# History 7077# 2008/07/23 : pda/jean : design 7078# 2008/07/24 : pda/jean : use idcor instead of idgrp 7079# 2010/12/01 : pda : i18n 7080# 2010/12/05 : pda : use tabuid instead of idcor 7081# 7082 7083proc menu-sendsmtp {dbfd field _tabuid sendsmtp} { 7084 upvar $_tabuid tabuid 7085 7086 # 7087 # Get group access right, in order to display or hide the button 7088 # 7089 7090 7091 if {$tabuid(p_smtp)} then { 7092 set title [mc "Use SMTP"] 7093 set html [::webapp::form-bool $field $sendsmtp] 7094 } else { 7095 set title "" 7096 set html [::webapp::form-hidden $field $sendsmtp] 7097 } 7098 7099 return [list $title $html] 7100} 7101 7102# 7103# Get an HTML input form for a host TTL value, or a hidden field 7104# if the group do not have the according right. 7105# 7106# Input: 7107# - dbfd : database handle 7108# - field : field name 7109# - _tabuid : user characteristics 7110# - ttl : default value 7111# Output: 7112# - return value: ready to use HTML string 7113# 7114# History 7115# 2010/10/31 : pda : design 7116# 2010/12/01 : pda : i18n 7117# 2010/12/05 : pda : use tabuid instead of idcor 7118# 7119 7120proc menu-ttl {dbfd field _tabuid ttl} { 7121 upvar $_tabuid tabuid 7122 7123 # 7124 # Convert the TTL value from the database in something which can be 7125 # displayed: the value "-1" means "no TTL set for this host", which 7126 # should be displayed as an empty string. 7127 # 7128 7129 if {$ttl == -1} then { 7130 set ttl "" 7131 } 7132 7133 # 7134 # Get the group permission. 7135 # 7136 7137 if {$tabuid(p_ttl)} then { 7138 set title [mc "TTL"] 7139 set html [::webapp::form-text $field 1 6 10 $ttl] 7140 append html " " 7141 append html [mc "(in seconds)"] 7142 } else { 7143 set title "" 7144 set html [::webapp::form-hidden $field $ttl] 7145 } 7146 7147 return [list $title $html] 7148} 7149 7150 7151# 7152# Get an HTML menu to select a domain. This may be either a simple 7153# text with a hidden field if the group has access to only one domain, 7154# or a dropdown menu. 7155# 7156# Input: 7157# - dbfd : database handle 7158# - idcor : user id 7159# - field : field name 7160# - where : SQL where clause (without SQL keyword "where") or empty string 7161# - sel : name of domain to pre-select, or empty string 7162# Output: 7163# - return value: HTML string 7164# 7165# History : 7166# 2002/04/11 : pda/jean : coding 7167# 2002/04/23 : pda : add display priority 7168# 2002/05/03 : pda/jean : migrated in libdns 7169# 2002/05/06 : pda/jean : use groups 7170# 2003/04/24 : pda/jean : decompose in two procedures 7171# 2004/02/06 : pda/jean : add where clause 7172# 2004/02/12 : pda/jean : add sel parameter 7173# 2010/11/15 : pda : delete err parameter 7174# 7175 7176proc menu-domain {dbfd idcor field where sel} { 7177 set lcouples [couple-domains $dbfd $idcor $where] 7178 7179 set lsel [lsearch -exact $lcouples [list $sel $sel]] 7180 if {$lsel == -1} then { 7181 set lsel {} 7182 } 7183 7184 # 7185 # If there is only one domain, present it as a text. If more 7186 # than one domain, use a dropdown menu. 7187 # 7188 7189 set ndom [llength $lcouples] 7190 switch -- $ndom { 7191 0 { 7192 d error [mc "Sorry, but you do not have any active domain"] 7193 } 7194 1 { 7195 set v [lindex [lindex $lcouples 0] 0] 7196 set h [::webapp::form-hidden $field $v] 7197 set html "$v $h" 7198 } 7199 default { 7200 set html [::webapp::form-menu $field 1 0 $lcouples $lsel] 7201 } 7202 } 7203 7204 return $html 7205} 7206 7207# 7208# Returns a list of couples {name name} for each authorized domain 7209# 7210# Input: 7211# - dbfd : database handle 7212# - idcor : user id 7213# - where : SQL where clause (without SQL keyword "where") or empty string 7214# Output: 7215# - return value: liste of couples 7216# 7217# History : 7218# 2003/04/24 : pda/jean : coding 7219# 2004/02/06 : pda/jean : add where clause 7220# 2010/12/01 : pda : i18n 7221# 7222 7223proc couple-domains {dbfd idcor where} { 7224 if {$where ne ""} then { 7225 set where " AND $where" 7226 } 7227 7228 set lcouples {} 7229 set sql "SELECT domain.name 7230 FROM dns.domain, dns.p_dom, global.nmuser 7231 WHERE domain.iddom = p_dom.iddom 7232 AND p_dom.idgrp = nmuser.idgrp 7233 AND nmuser.idcor = $idcor 7234 $where 7235 ORDER BY p_dom.sort ASC, domain.name ASC" 7236 pg_select $dbfd $sql tab { 7237 lappend lcouples [list $tab(name) $tab(name)] 7238 } 7239 7240 return $lcouples 7241} 7242 7243# 7244# Get an HTML menu to select one view. This may be either a simple 7245# text with a hidden field if the group has access to only one view, 7246# or a menu. 7247# 7248# Input: 7249# - dbfd : database handle 7250# - idcor : user id 7251# - field : field name 7252# - sel : list of view id to pre-select, or empty list to pre-select 7253# default views (those cited in the p_view.selected column) 7254# Output: 7255# - return value: list {disp html} where disp=true if view menu 7256# must be displayed, and html is html code (may be of "hidden" 7257# input type) to be inserted. 7258# 7259# History : 7260# 2012/10/30 : pda/jean : design 7261# 2012/11/07 : pda/jean : add mult parameter and change return value 7262# 2013/04/10 : pda/jean : remove mult parameter 7263# 7264 7265proc menu-view {dbfd idcor field sel} { 7266 set nsel [llength $sel] 7267 set lsel {} 7268 set lcouples {} 7269 set sql "SELECT v.idview, v.name, p.selected 7270 FROM dns.view v, dns.p_view p, global.nmuser 7271 WHERE nmuser.idcor = $idcor 7272 AND p.idgrp = nmuser.idgrp 7273 AND v.idview = p.idview 7274 ORDER BY p.sort ASC, v.name ASC" 7275 set i 0 7276 pg_select $dbfd $sql tab { 7277 lappend lcouples [list $tab(idview) $tab(name)] 7278 if {$nsel == 0} then { 7279 # no sel parameter given: use selected views for this group 7280 if {$tab(selected)} then { 7281 lappend lsel $i 7282 } 7283 } else { 7284 # sel is a list of idviews 7285 # search our idview in sel 7286 if {[lsearch -exact $sel $tab(idview)] != -1} then { 7287 lappend lsel $i 7288 } 7289 } 7290 incr i 7291 } 7292 7293 set nviews [llength $lcouples] 7294 switch $nviews { 7295 0 { 7296 d error [mc "Sorry, but you do not have access to any view"] 7297 } 7298 1 { 7299 set idview [lindex [lindex $lcouples 0] 0] 7300 set disp 0 7301 set html [::webapp::form-hidden $field $idview] 7302 } 7303 default { 7304 set disp 1 7305 set html [::webapp::form-menu $field 1 0 $lcouples $lsel] 7306 } 7307 } 7308 7309 return [list $disp $html] 7310} 7311 7312############################################################################## 7313# Network management 7314############################################################################## 7315 7316# 7317# Return list of networks for a given group and a given privilege 7318# 7319# Input: 7320# - parameters: 7321# - dbfd : database handle 7322# - idgrp : group id 7323# - priv : "consult", "dhcp" or "acl" 7324# Output: 7325# - return value: list of networks {idnet cidr4 cidr6 name} 7326# 7327# History 7328# 2004/01/16 : pda/jean : specification and design 7329# 2004/08/06 : pda/jean : extend permissions on networks 7330# 2004/10/05 : pda/jean : adapt to new permissions 7331# 2006/05/24 : pda/jean/boggia : extract in a primary function 7332# 2010/12/01 : pda : i18n 7333# 7334 7335proc allowed-networks {dbfd idgrp priv} { 7336 # 7337 # Build a WHERE clause from the given privilege 7338 # 7339 7340 switch -- $priv { 7341 consult { 7342 set w1 "" 7343 set w2 "" 7344 } 7345 dhcp { 7346 set w1 "AND p.$priv > 0" 7347 set w2 "AND n.$priv > 0" 7348 } 7349 acl { 7350 set w1 "AND p.$priv > 0" 7351 set w2 "" 7352 } 7353 } 7354 7355 # 7356 # Get all allowed networks for this group and for this privilege 7357 # 7358 7359 set lnet {} 7360 set sql "SELECT n.idnet, n.name, n.addr4, n.addr6 7361 FROM dns.network n, dns.p_network p 7362 WHERE n.idnet = p.idnet 7363 AND p.idgrp = $idgrp 7364 $w1 $w2 7365 ORDER BY addr4, addr6" 7366 pg_select $dbfd $sql tab { 7367 lappend lnet [list $tab(idnet) $tab(addr4) $tab(addr6) $tab(name)] 7368 } 7369 7370 return $lnet 7371} 7372 7373# 7374# Return list of allowed vlan-id of L2-only networks for a given group 7375# 7376# Input: 7377# - parameters: 7378# - dbfd : database handle 7379# - idgrp : group id 7380# Output: 7381# - return value: list of vlan ids 7382# 7383# History 7384# 2013/01/24 : jean : adaptated from allowed-networks 7385# 7386 7387proc allowed-l2only {dbfd idgrp} { 7388 # 7389 # Get all allowed vlans for this group 7390 # 7391 7392 set lvlan {} 7393 set sql "SELECT vlanid FROM topo.p_l2only WHERE idgrp = $idgrp" 7394 pg_select $dbfd $sql tab { 7395 lappend lvlan $tab(vlanid) 7396 } 7397 7398 return $lvlan 7399} 7400 7401# 7402# Returns the list of networks allowed for a group (with a given privilege) 7403# ready to use with form-menu. 7404# 7405# Input: 7406# - parameters: 7407# - dbfd : database handle 7408# - idgrp : group id 7409# - priv : "consult", "dhcp" or "acl" 7410# Output: 7411# - return value: list of elements {id name} 7412# 7413# History 7414# 2006/05/24 : pda/jean/boggia : extract procedure heart in allowed-networks 7415# 2010/12/01 : pda : i18n 7416# 2012/04/26 : pda : fix bug where non-html chars are replaced here 7417# 7418 7419proc read-networks {dbfd idgrp priv} { 7420 set lnet {} 7421 foreach r [allowed-networks $dbfd $idgrp $priv] { 7422 lassign $r idnet cidr4 cidr6 name 7423 lappend lnet [list $idnet [format "%s\t%s\t(%s)" $cidr4 $cidr6 $name]] 7424 } 7425 return $lnet 7426} 7427 7428# 7429# Check a network id as returned in a form field. This check is done 7430# according to a given group and a given privilege. 7431# 7432# Input: 7433# - parameters: 7434# - dbfd : database handle 7435# - netid : id of network to check 7436# - idgrp : group id 7437# - priv : "consult", "dhcp" or "acl" 7438# - version : 4, 6 or {4 6} 7439# - _msg : empty string or error message 7440# Output: 7441# - return value: list of cidr 7442# - parameter _msg : empty string or error message 7443# 7444# History 7445# 2004/10/05 : pda/jean : specification and design 7446# 2010/12/01 : pda : i18n 7447# 7448 7449proc check-netid {dbfd netid idgrp priv version _msg} { 7450 upvar $_msg msg 7451 7452 # 7453 # Check syntax of id 7454 # 7455 set netid [string trim $netid] 7456 if {! [regexp {^[0-9]+$} $netid]} then { 7457 set msg [mc "Invalid network id '%s'" $netid] 7458 return {} 7459 } 7460 7461 # 7462 # Convert privilege into an sql where clause 7463 # 7464 7465 switch -- $priv { 7466 consult { 7467 set w1 "" 7468 set w2 "" 7469 set c [mc "You cannot read this network"] 7470 } 7471 dhcp { 7472 set w1 "AND p.$priv > 0" 7473 set w2 "AND n.$priv > 0" 7474 set c [mc "You do not have DHCP access to this network"] 7475 } 7476 acl { 7477 set w1 "AND p.$priv > 0" 7478 set w2 "" 7479 set c [mc "You do not have ACL access to this network"] 7480 } 7481 } 7482 7483 # 7484 # Check network and read associated CIDR(s) 7485 # 7486 7487 set lcidr {} 7488 set msg "" 7489 7490 set sql "SELECT n.addr4, n.addr6 7491 FROM dns.p_network p, dns.network n 7492 WHERE p.idgrp = $idgrp 7493 AND p.idnet = n.idnet 7494 AND n.idnet = $netid 7495 $w1 $w2" 7496 set cidrplage4 "" 7497 set cidrplage6 "" 7498 pg_select $dbfd $sql tab { 7499 set cidrplage4 $tab(addr4) 7500 set cidrplage6 $tab(addr6) 7501 } 7502 7503 if {[lsearch -exact $version 4] == -1} then { 7504 set cidrplage4 "" 7505 } 7506 if {[lsearch -exact $version 6] == -1} then { 7507 set cidrplage6 "" 7508 } 7509 7510 set empty4 [string equal $cidrplage4 ""] 7511 set empty6 [string equal $cidrplage6 ""] 7512 7513 switch -glob $empty4-$empty6 { 7514 1-1 { 7515 set msg $c 7516 } 7517 0-1 { 7518 lappend lcidr $cidrplage4 7519 } 7520 1-0 { 7521 lappend lcidr $cidrplage6 7522 } 7523 0-0 { 7524 lappend lcidr $cidrplage4 7525 lappend lcidr $cidrplage6 7526 } 7527 } 7528 7529 return $lcidr 7530} 7531 7532############################################################################## 7533# Edition of tabular data 7534############################################################################## 7535 7536# 7537# Generate HTML code to display and edit table content. 7538# 7539# Input: 7540# - parameters: 7541# - cwidth : list of column widths {w1 w2 ... wn} (unit = %) 7542# - ctitle : list of column titles specification, each element 7543# is {type value} where type = "html" or "text" 7544# - cspec : list of column specifications, each element 7545# is {id type defval}, where 7546# - id : column id in the table, and name of field (idNN or idnNN) 7547# - type : "text", "string N", "int N", "bool", "menu L", 7548# "textarea {W H}" or "image URL" 7549# - defval : default value for new lines 7550# - dbfd : database handle 7551# - sql : SQL request to get column values (notably the id column) 7552# - idnum : column name of the numeric id 7553# - _tab : in return, will contail the generated HTML code 7554# Output: 7555# - return value: empty string or error message 7556# - parameter _tab : HTML code 7557# 7558# History 7559# 2001/11/01 : pda : specification and documentation 7560# 2001/11/01 : pda : coding 7561# 2002/05/03 : pda/jean : add type menu 7562# 2002/05/06 : pda/jean : add type textarea 7563# 2002/05/16 : pda : convert to arrgen 7564# 2010/12/04 : pda : i18n 7565# 7566 7567proc display-tabular {cwidth ctitle cspec dbfd sql idnum _tab} { 7568 upvar $_tab tab 7569 7570 # 7571 # Minimal integrity test on column number. 7572 # 7573 7574 if {[llength $ctitle] != [llength $cspec] || \ 7575 [llength $ctitle] != [llength $cwidth]} then { 7576 return [mc "Internal error: invalid tabular specification"] 7577 } 7578 7579 # 7580 # Build-up the arrgen array specification. 7581 # 7582 7583 set aspec [_build-array-spec $cwidth $ctitle $cspec] 7584 set lines {} 7585 7586 # 7587 # Display title line 7588 # 7589 7590 set l {} 7591 lappend l "Title" 7592 foreach t $ctitle { 7593 lappend l [lindex $t 1] 7594 } 7595 lappend lines $l 7596 7597 # 7598 # Display existing lines from the database 7599 # 7600 7601 pg_select $dbfd $sql tabsql { 7602 set tabsql(:$idnum) $tabsql($idnum) 7603 lappend lines [_display-tabular-line $cspec tabsql $idnum "existing"] 7604 } 7605 7606 # 7607 # Add empty lines at the end to let user enter new values 7608 # 7609 7610 foreach s $cspec { 7611 lassign $s id type defval 7612 set tabdef($id) $defval 7613 } 7614 7615 for {set i 1} {$i <= 5} {incr i} { 7616 set tabdef(:$idnum) "n$i" 7617 lappend lines [_display-tabular-line $cspec tabdef $idnum "new"] 7618 } 7619 7620 # 7621 # Generates HTML code and returns 7622 # 7623 7624 set tab [::arrgen::output "html" $aspec $lines] 7625 7626 return "" 7627} 7628 7629# 7630# Build-up a table specification (for arrgen) from display-tabular parameters 7631# 7632# Input: 7633# - parameters: see display-tabular 7634# Output: 7635# - return value: an "arrgen" specification 7636# 7637# History 7638# 2001/11/01 : pda : design and documentation 7639# 2002/05/16 : pda : convert to arrgen 7640# 2010/12/04 : pda : i18n 7641# 7642 7643proc _build-array-spec {cwidth ctitle cspec} { 7644 # 7645 # First, build-up Title pattern 7646 # 7647 7648 set titpat "pattern Title {" 7649 foreach t $ctitle { 7650 append titpat "vbar {yes} " 7651 append titpat "chars {bold} " 7652 append titpat "align {center} " 7653 append titpat "column { " 7654 append titpat " botbar {yes} " 7655 if {[lindex $t 0] ne "text"} then { 7656 append titpat " format {raw} " 7657 } 7658 append titpat "} " 7659 } 7660 append titpat "vbar {yes} " 7661 append titpat "} " 7662 7663 # 7664 # Next, normal lines 7665 # 7666 7667 set norpat "pattern Normal {" 7668 foreach t $cspec { 7669 append norpat "topbar {yes} " 7670 append norpat "vbar {yes} " 7671 append norpat "column { " 7672 append norpat " align {center} " 7673 append norpat " botbar {yes} " 7674 set type [lindex [lindex $t 1] 0] 7675 if {$type ne "text"} then { 7676 append norpat " format {raw} " 7677 } 7678 append norpat "} " 7679 } 7680 append norpat "vbar {yes} " 7681 append norpat "} " 7682 7683 # 7684 # Finally, global specifications 7685 # 7686 7687 return "global { chars {10 normal} columns {$cwidth} } $titpat $norpat" 7688} 7689 7690# 7691# Display a line of tabular data 7692# 7693# Input: 7694# - parameters: 7695# - cspec : see display-tabular 7696# - tab : array indexed by fields specified in cspec (see display-tabular) 7697# - idnum : column name of the numeric id 7698# - new : "existing" or "new" 7699# Output: 7700# - return value: an "arrgen" line 7701# 7702# History 7703# 2001/11/01 : pda : specification and documentation 7704# 2001/11/01 : pda : design 7705# 2002/05/03 : pda/jean : add type menu 7706# 2002/05/06 : pda/jean : add type textarea 7707# 2002/05/16 : pda : convert to arrgen 7708# 2010/12/04 : pda : i18n 7709# 2012/01/02 : pda : add parameter new 7710# 7711 7712proc _display-tabular-line {cspec _tab idnum new} { 7713 upvar $_tab tab 7714 7715 set line {Normal} 7716 foreach s $cspec { 7717 lassign $s id type defval 7718 7719 set value $tab($id) 7720 lassign $type typekw typeopt 7721 7722 set num $tab(:$idnum) 7723 set ref $id$num 7724 7725 switch $typekw { 7726 text { 7727 set item $value 7728 } 7729 string { 7730 set item [::webapp::form-text $ref 1 $typeopt 0 $value] 7731 } 7732 int { 7733 set item [::webapp::form-text $ref 1 $typeopt 0 $value] 7734 } 7735 bool { 7736 set item [::webapp::form-bool $ref $value] 7737 } 7738 menu { 7739 set sel 0 7740 set i 0 7741 foreach e $typeopt { 7742 set v [lindex $e 0] 7743 if {$v eq $value} then { 7744 set sel $i 7745 } 7746 incr i 7747 } 7748 set item [::webapp::form-menu $ref 1 0 $typeopt [list $sel]] 7749 } 7750 textarea { 7751 lassign $typeopt width height 7752 set item [::webapp::form-text $ref $height $width 0 $value] 7753 } 7754 image { 7755 if {$new eq "new"} then { 7756 set item " " 7757 } else { 7758 set item [format $typeopt $num] 7759 } 7760 } 7761 } 7762 lappend line $item 7763 } 7764 7765 return $line 7766} 7767 7768############################################################################## 7769# Storing tabular data 7770############################################################################## 7771 7772# 7773# Get modifications from a form generated by display-tabular and 7774# store them if necessary in the database. 7775# 7776# Input: 7777# - parameters: 7778# - dbfd : database handle 7779# - cspec : column specifications (see below) 7780# - idnum : column name of the numeric id 7781# - table : name of the SQL table to modify 7782# - _ftab : array containing form field values 7783# - check : name of a procedure to call on complete row 7784# Output: 7785# - return value: none, this function exits if an error is encountered 7786# 7787# Notes : 7788# - format of "cspec" is {{column type defval} ...}, where: 7789# - column: column id in the table 7790# - type : "text", "string N", "int N", "bool", "menu L", 7791# "textarea {W H}" or "image URL" 7792# - defval: the default value to store in the table 7793# if the value is not provided 7794# - first column of "cspec" is the key used to know if an entry must 7795# be added or deleted. 7796# - the check procedure will be called with parameters: 7797# $check op dbfd _msg id idnum table _tabval 7798# where: 7799# - op : nop, mod, add, del 7800# - dbfd : database handle 7801# - _msg : error message if any 7802# - id : id (value) of entry to modify (null if op == add) 7803# - idnum : column name of the numeric id 7804# - table : name of the SQL table to modify 7805# - _tabval : array containing new values (null if op == del) 7806# the check procedure may modify _tabval. 7807# It must returns 1 (ok) or 0 (err) 7808# 7809# History 7810# 2001/11/02 : pda : specification and documentation 7811# 2001/11/02 : pda : coding 7812# 2002/05/03 : pda/jean : remove an old constraint 7813# 2010/12/04 : pda : i18n 7814# 2010/12/14 : pda : use db lock methods 7815# 2012/01/03 : pda : use ftab indexes rather than count until max index 7816# 2012/01/09 : pda : add type to cspec and check parameter 7817# 7818 7819proc store-tabular {dbfd cspec idnum table _ftab check} { 7820 upvar $_ftab ftab 7821 7822 # 7823 # Lock the table 7824 # 7825 7826 d dblock [list $table] 7827 7828 # 7829 # Get used ids 7830 # 7831 7832 set key [lindex [lindex $cspec 0] 0] 7833 7834 set lid [array names ftab -regexp "^$key\[0-9\]+$"] 7835 regsub -all "\[\[:<:\]\]($key)(\[0-9\])" $lid {\2} lid 7836 set lid [lsort -increasing $lid] 7837 7838 # 7839 # Get old ids, if we have to output a precise error message 7840 # when SQL transaction has aborted. 7841 # 7842 7843 pg_select $dbfd "SELECT $key, $idnum FROM $table" tab { 7844 set okey $tab($idnum) 7845 set oldkeys($okey) $tab($key) 7846 } 7847 7848 # 7849 # Traversal of existing ids in the database 7850 # 7851 7852 foreach id $lid { 7853 if {[info exists ftab(${key}${id})]} { 7854 _fill-tabval $cspec "" $id ftab tabval 7855 7856 if {$tabval($key) eq ""} then { 7857 # 7858 # Delete entry 7859 # 7860 7861 set ok [_store-tabular-del $dbfd msg $id $idnum $table $check] 7862 if {! $ok} then { 7863 # 7864 # Deletion is not possible. Transaction may have been 7865 # aborted. Look into the saved keys 7866 # 7867 set okey "" 7868 if {[info exists oldkeys($id)]} then { 7869 set okey $oldkeys($id) 7870 } 7871 d dbabort [mc "delete %s" $okey] $msg 7872 } 7873 } else { 7874 # 7875 # Modify entry 7876 # 7877 7878 set ok [_store-tabular-mod $dbfd msg $id $idnum $table tabval $check] 7879 if {! $ok} then { 7880 d dbabort [mc "modify %s" $tabval($key)] $msg 7881 } 7882 } 7883 } 7884 } 7885 7886 # 7887 # New entries 7888 # 7889 7890 set idnew 1 7891 while {[info exists ftab(${key}n${idnew})]} { 7892 _fill-tabval $cspec "n" $idnew ftab tabval 7893 7894 if {$tabval($key) ne ""} then { 7895 # 7896 # Add entry 7897 # 7898 7899 set ok [_store-tabular-add $dbfd msg $table tabval $check] 7900 if {! $ok} then { 7901 d dbabort [mc "add %s" $tabval($key)] $msg 7902 } 7903 } 7904 7905 incr idnew 7906 } 7907 7908 # 7909 # Unlock and commit modifications 7910 # 7911 7912 d dbcommit [mc "store"] 7913} 7914 7915# 7916# Read form field values, and add default values, notably for boolean 7917# types (checkboxes) which may be not present. 7918# 7919# Input: 7920# - parameters: 7921# - cspec : see store-tabular 7922# - prefix : "" (existing entry) or "n" (new entry) 7923# - num : entry number 7924# - _ftab : form field values (see webapp/get-data) 7925# - _tabval : array to fill 7926# Output: 7927# - return value: none 7928# - parameter _tabval : array filled with usable values 7929# 7930# Example : 7931# - if cspec = {{login} {name}} and prefix = "n" and num = "5" 7932# then we search ftab(loginn5) et ftab(namen5) and we place found 7933# (or default) values in in tabval(login) and tabval(name) 7934# 7935# History : 7936# 2001/04/01 : pda : design 7937# 2001/04/03 : pda : documentation 7938# 2001/11/02 : pda : extension 7939# 2010/12/04 : pda : i18n 7940# 7941 7942proc _fill-tabval {cspec prefix num _ftab _tabval} { 7943 upvar $_ftab ftab 7944 upvar $_tabval tabval 7945 7946 catch {unset tabval} 7947 7948 foreach c $cspec { 7949 lassign $c var type defval 7950 7951 set form ${var}${prefix}${num} 7952 7953 if {[info exists ftab($form)]} then { 7954 set tabval($var) [string trim [lindex $ftab($form) 0]] 7955 } else { 7956 switch [lindex $type 0] { 7957 bool { 7958 # boolean not checked is absent from form values 7959 set tabval($var) 0 7960 } 7961 image { 7962 # don't set variable 7963 # the generated value is used as a comparison 7964 # in order to check if value has been modified 7965 } 7966 default { 7967 set tabval($var) $defval 7968 } 7969 } 7970 } 7971 } 7972} 7973 7974# 7975# Modify an entry 7976# 7977# Input: 7978# - parameters: 7979# - dbfd : database handle 7980# - _msg : in return, error message if any 7981# - id : id (value) of entry to modify 7982# - idnum : column name of the numeric id 7983# - table : name of the SQL table to modify 7984# - _tabval : array containing new values 7985# - check : name of a procedure to call 7986# Output: 7987# - return value: 1 if ok, 0 if error 7988# - parameters: 7989# - msg : error message if an error occurred 7990# 7991# History : 7992# 2001/04/01 : pda : design 7993# 2001/04/03 : pda : documentation 7994# 2001/11/02 : pda : generalization 7995# 2004/01/20 : pda/jean : add NULL if empty string (for ipv6) 7996# 2010/12/04 : pda : i18n 7997# 7998 7999proc _store-tabular-mod {dbfd _msg id idnum table _tabval check} { 8000 upvar $_msg msg 8001 upvar $_tabval tabval 8002 8003 # 8004 # There is no need to modify anything if all values are identical. 8005 # 8006 8007 set same 1 8008 pg_select $dbfd "SELECT * FROM $table WHERE $idnum = $id" tab { 8009 foreach attribut [array names tabval] { 8010 if {$tabval($attribut) ne $tab($attribut)} then { 8011 set same 0 8012 break 8013 } 8014 } 8015 } 8016 8017 if {$same} then { 8018 set ok [$check "nop" $dbfd msg $id $idnum $table tabval] 8019 } else { 8020 # 8021 # It's different, we must do the work... 8022 # 8023 8024 set ok [$check "mod" $dbfd msg $id $idnum $table tabval] 8025 if {$ok} then { 8026 set l {} 8027 foreach attr [array names tabval] { 8028 if {$tabval($attr) eq ""} then { 8029 set v "NULL" 8030 } else { 8031 set v "'[::pgsql::quote $tabval($attr)]'" 8032 } 8033 lappend l "$attr = $v" 8034 } 8035 set sql "UPDATE $table SET [join $l ,] WHERE $idnum = $id" 8036 set ok [::pgsql::execsql $dbfd $sql msg] 8037 } 8038 } 8039 8040 return $ok 8041} 8042 8043# 8044# Entry deletion 8045# 8046# Input: 8047# - parameters: 8048# - dbfd : database handle 8049# - _msg : in return, error message if any 8050# - id : id (value) of entry to delete 8051# - idnum : column name of the numeric id 8052# - table : name of the SQL table to modify 8053# Output: 8054# - return value: 1 if ok, 0 if error 8055# - parameters: 8056# - msg : error message if an error occurred 8057# 8058# History : 8059# 2001/04/03 : pda : design 8060# 2001/11/02 : pda : generalization 8061# 2002/05/03 : pda/jean : remove an old constraint 8062# 2010/12/04 : pda : i18n 8063# 8064 8065proc _store-tabular-del {dbfd _msg id idnum table check} { 8066 upvar $_msg msg 8067 8068 set ok [$check "del" $dbfd msg $id $idnum $table {}] 8069 if {$ok} then { 8070 set sql "DELETE FROM $table WHERE $idnum = $id" 8071 set ok [::pgsql::execsql $dbfd $sql msg] 8072 } 8073 return $ok 8074} 8075 8076# 8077# Entry addition 8078# 8079# Input: 8080# - parameters: 8081# - dbfd : database handle 8082# - _msg : in return, error message if any 8083# - table : name of the SQL table to modify 8084# - _tabval : array containing new values 8085# Output: 8086# - return value: 1 if ok, 0 if error 8087# - parameters: 8088# - msg : error message if an error occurred 8089# 8090# History : 8091# 2001/04/01 : pda : design 8092# 2001/04/03 : pda : documentation 8093# 2001/11/02 : pda : generalization 8094# 2004/01/20 : pda/jean : add NULL attribute if empty string (for ipv6) 8095# 2010/12/04 : pda : i18n 8096# 8097 8098proc _store-tabular-add {dbfd _msg table _tabval check} { 8099 upvar $_msg msg 8100 upvar $_tabval tabval 8101 8102 set ok [$check "add" $dbfd msg {} {} $table tabval] 8103 if {$ok} then { 8104 # 8105 # Column names 8106 # 8107 set cols [array names tabval] 8108 8109 # 8110 # Column values 8111 # 8112 set vals {} 8113 foreach c $cols { 8114 if {$tabval($c) eq ""} then { 8115 set v "NULL" 8116 } else { 8117 set v "'[::pgsql::quote $tabval($c)]'" 8118 } 8119 lappend vals $v 8120 } 8121 8122 set sql "INSERT INTO $table ([join $cols ,]) VALUES ([join $vals ,])" 8123 set ok [::pgsql::execsql $dbfd $sql msg] 8124 } 8125 return $ok 8126} 8127 8128############################################################################## 8129# Internal authentication functions 8130############################################################################## 8131 8132# 8133# Internal (PostgreSQL) authenticaion management 8134# 8135# Historique 8136# 2003/05/30 : pda/jean : design 8137# 2003/06/12 : pda/jean : remove lsuser 8138# 2003/06/13 : pda/jean : add genpw, chpw and showuser 8139# 2003/06/27 : pda : add edituser 8140# 2003/07/28 : pda : split name and christian name 8141# 2003/12/11 : pda : simplify 8142# 2005/05/25 : pda/jean : use ldap 8143# 2005/06/07 : pda/jean/zamboni : crypt command 8144# 2005/08/24 : pda : add ldap port 8145# 2007/10/04 : jean : ldap directory is no longer modified in setuser 8146# 2007/11/29 : pda/jean : merge old auth.tcl package and libauth.tcl 8147# 2011/01/02 : pda : integration of libauth in libdns 8148# 8149 8150# Fields in pgauth.user database table 8151set libconf(fields) {login password lastname firstname mail phone mobile fax addr} 8152 8153# Fields : <title> <field spec> <form var name> <user> 8154# with <user> = 1 if field contains information about user (else : search only) 8155set libconf(editfields) { 8156 {Login {string 10} login 1} 8157 {Name {string 40} lastname 1} 8158 {Method {yesno {%1$s Regular expression %2$s Phonetic}} phren 0} 8159 {{First name} {string 40} firstname 1} 8160 {Method {yesno {%1$s Regular expression %2$s Phonetic}} phrep 0} 8161 {Address {text 3 40} addr 1} 8162 {Mail {string 40} mail 1} 8163 {Phone {string 15} phone 1} 8164 {Fax {string 15} fax 1} 8165 {Mobile {string 15} mobile 1} 8166} 8167set libconf(editrealms) { 8168 {{Realms} {list multi ...} realms 1} 8169} 8170 8171# 8172# Tabular formats (see arrgen(n)): 8173# - tabuchoice : user selection with clickable login 8174# - tabumod : user add/modify form 8175# - tabulist : user list (to display or print) 8176# 8177 8178set libconf(tabuchoice) { 8179 global { 8180 chars {10 normal} 8181 align {left} 8182 botbar {yes} 8183 columns {11 26 35 28 10} 8184 latex { 8185 linewidth {267} 8186 } 8187 } 8188 pattern Title { 8189 title {yes} 8190 topbar {yes} 8191 chars {bold} 8192 align {center} 8193 vbar {yes} 8194 column { } 8195 vbar {yes} 8196 column { } 8197 vbar {yes} 8198 column { } 8199 vbar {yes} 8200 column { } 8201 vbar {yes} 8202 column { } 8203 vbar {yes} 8204 } 8205 pattern User { 8206 vbar {yes} 8207 column { 8208 format {raw} 8209 } 8210 vbar {yes} 8211 column { } 8212 vbar {yes} 8213 column { } 8214 vbar {yes} 8215 column { } 8216 vbar {yes} 8217 column { } 8218 vbar {yes} 8219 } 8220} 8221 8222set libconf(tabumod) { 8223 global { 8224 align {left} 8225 botbar {no} 8226 columns {25 75} 8227 } 8228 pattern {Normal} { 8229 vbar {no} 8230 column { } 8231 vbar {no} 8232 column { 8233 format {raw} 8234 } 8235 vbar {no} 8236 } 8237} 8238 8239set libconf(tabulist) { 8240 global { 8241 chars {10 normal} 8242 align {left} 8243 botbar {yes} 8244 columns {8 16 32 10 10 10 14 10} 8245 latex { 8246 linewidth {267} 8247 } 8248 } 8249 pattern Title { 8250 title {yes} 8251 topbar {yes} 8252 chars {bold} 8253 align {center} 8254 vbar {yes} 8255 column { } 8256 vbar {yes} 8257 column { } 8258 vbar {yes} 8259 column { } 8260 vbar {yes} 8261 column { } 8262 vbar {yes} 8263 column { } 8264 vbar {yes} 8265 column { } 8266 vbar {yes} 8267 column { } 8268 vbar {yes} 8269 column { } 8270 vbar {yes} 8271 } 8272 pattern User { 8273 chars {8} 8274 vbar {yes} 8275 column { } 8276 vbar {yes} 8277 column { } 8278 vbar {yes} 8279 column { } 8280 vbar {yes} 8281 column { } 8282 vbar {yes} 8283 column { } 8284 vbar {yes} 8285 column { } 8286 vbar {yes} 8287 column { } 8288 vbar {yes} 8289 column { } 8290 vbar {yes} 8291 } 8292} 8293 8294###################################### 8295# User management 8296###################################### 8297 8298# 8299# Read user entry 8300# 8301# Input: 8302# - parameters : 8303# - dbfd : database handle 8304# - login : user login 8305# - tab : array containing, in return, user information 8306# Output: 8307# - return value : 1 if found, 0 if not found 8308# - parameter tab : 8309# tab(login) login 8310# tab(lastname) name 8311# tab(firstname) christian name 8312# tab(mail) email address 8313# tab(phone) phone number 8314# tab(fax) facsimile number 8315# tab(mobile) mobile phone number 8316# tab(addr) postal address 8317# tab(encryption) "crypt" if password is encrypted 8318# tab(password) password (crypted or not) 8319# tab(realms) list of realms to which user belongs 8320# 8321# History 8322# 2003/05/13 : pda/jean : design 8323# 2003/05/30 : pda/jean : add realms 8324# 2005/05/25 : pda/jean : add ldap code 8325# 2007/12/04 : pda/jean : remove ldap code 8326# 2010/12/29 : pda : i18n and netmagis merge 8327# 8328 8329proc pgauth-getuser {dbfd login _tab} { 8330 upvar $_tab tab 8331 global libconf 8332 8333 set found 0 8334 set qlogin [::pgsql::quote $login] 8335 set sql "SELECT * FROM pgauth.user WHERE login = '$qlogin'" 8336 pg_select $dbfd $sql tabsql { 8337 foreach c $libconf(fields) { 8338 set tab($c) $tabsql($c) 8339 } 8340 set found 1 8341 } 8342 set tab(realms) {} 8343 set sql "SELECT realm FROM pgauth.member WHERE login = '$qlogin'" 8344 pg_select $dbfd $sql tabsql { 8345 lappend tab(realms) $tabsql(realm) 8346 } 8347 return $found 8348} 8349 8350# 8351# Modify or create a user 8352# 8353# Input: 8354# - parameters : 8355# - dbfd : database handle 8356# - tab : see getuser 8357# - transact : "transaction" (by default) or "no transaction" 8358# Output: 8359# - return value : empty string or error message 8360# 8361# Note : if password field is nul, a crypted "*" is set by default 8362# (meaning that this account is not active) 8363# 8364# History 8365# 2003/05/13 : pda/jean : design 8366# 2003/05/30 : pda/jean : add realms 8367# 2003/08/05 : pda : add transactions 8368# 2007/12/04 : pda/jean : specialization for postgresql 8369# 2010/12/29 : pda : i18n and netmagis merge 8370# 8371 8372proc pgauth-setuser {dbfd _tab {transact transaction}} { 8373 upvar $_tab tab 8374 global libconf 8375 8376 if {! [regexp -- {^[a-z][-a-z0-9\.]*$} $tab(login)]} then { 8377 return [mc {Invalid login syntax (^[a-z][-a-z0-9\.]*$)}] 8378 } 8379 8380 if {$transact eq "transaction"} then { 8381 set tr 1 8382 d dblock {pgauth.user pgauth.member} 8383 } else { 8384 set tr 0 8385 } 8386 8387 # 8388 # Remove user 8389 # 8390 set msg [pgauth-deluser $dbfd $tab(login) "no transaction"] 8391 if {$msg ne ""} then { 8392 if {$tr} then { 8393 d dbabort [mc "delete %s" $tab(login)] $msg 8394 } 8395 return $msg 8396 } 8397 8398 # 8399 # If password does not exist, invalid login 8400 # 8401 if {! [info exists tab(password)]} then { 8402 set tab(password) "*" 8403 } 8404 8405 # 8406 # Insert user data in database 8407 # 8408 set cols {} 8409 set vals {} 8410 foreach c $libconf(fields) { 8411 if {[info exists tab($c)]} then { 8412 lappend cols $c 8413 lappend vals "'[::pgsql::quote $tab($c)]'" 8414 } 8415 } 8416 set cols [join $cols ","] 8417 set vals [join $vals ","] 8418 set sql "INSERT INTO pgauth.user ($cols) VALUES ($vals)" 8419 if {![::pgsql::execsql $dbfd $sql msg]} then { 8420 if {$tr} then { 8421 d dbabort [mc "add %s" $tab(login)] $msg 8422 } 8423 return [mc {Unable to insert account '%1$s': %2$s} $tab(login) $msg] 8424 } 8425 8426 # 8427 # Insert membership 8428 # 8429 set sql "" 8430 foreach r $tab(realms) { 8431 append sql "INSERT INTO pgauth.member (login, realm) VALUES 8432 ('$tab(login)', '$r') ;" 8433 } 8434 if {! [::pgsql::execsql $dbfd $sql msg]} then { 8435 if {$tr} then { 8436 d dbabort [mc "add %s" $tab(login)] $msg 8437 } 8438 return [mc {Unable to insert '%1$s' membership: %2$s} $tab(login) $msg] 8439 } 8440 8441 # 8442 # Transaction end 8443 # 8444 if {$tr} then { 8445 d dbcommit [mc "add %s" $tab(login)] 8446 } 8447 8448 return "" 8449} 8450 8451# 8452# Remove user entry 8453# 8454# Input: 8455# - parameters : 8456# - dbfd : database handle 8457# - login : login name 8458# - transact : "transaction" (default) or "no transaction" 8459# Output: 8460# - return value : empty string or error message 8461# 8462# History 8463# 2003/05/13 : pda/jean : design 8464# 2003/05/30 : pda/jean : add realms 8465# 2007/12/04 : pda/jean : specialization for postgresql 8466# 2010/12/29 : pda : i18n and netmagis merge 8467# 8468 8469proc pgauth-deluser {dbfd login {transact transaction}} { 8470 if {$transact eq "transaction"} then { 8471 set tr 1 8472 d dblock {pgauth.user pgauth.member} 8473 } else { 8474 set tr 0 8475 } 8476 8477 set qlogin [::pgsql::quote $login] 8478 set sql "DELETE FROM pgauth.member WHERE login = '$qlogin'" 8479 if {! [::pgsql::execsql $dbfd $sql msg]} then { 8480 if {$tr} then { 8481 d dbabort [mc "delete %s" $login] $msg 8482 } 8483 return $msg 8484 } 8485 8486 set sql "DELETE FROM pgauth.user WHERE login = '$qlogin'" 8487 if {! [::pgsql::execsql $dbfd $sql msg]} then { 8488 if {$tr} then { 8489 d dbabort [mc "delete %s" $login] $msg 8490 } 8491 return $msg 8492 } 8493 8494 8495 if {$tr} then { 8496 d dbcommit [mc "add %s" $login] 8497 } 8498 8499 return "" 8500} 8501 8502# 8503# Search a user with criterion 8504# 8505# Input: 8506# - parameters : 8507# - dbfd : database handle 8508# - tabcrit : array containing criterion 8509# login, lastname, firstname, addr, mail, phone, mobile, 8510# fax, realm, or phlast, phfirst for phonetic searches 8511# - sort (optional) : list {sort...} where 8512# sort = +/- sort-criterion 8513# Output: 8514# - return value : list of found logins 8515# 8516# Note : each criterion is a regexp (* and ? only) 8517# 8518# History 8519# 2003/06/06 : pda/jean : design 8520# 2003/08/01 : pda/jean : phonetic criterions 8521# 2003/08/11 : pda : search "or" on more than one realm 8522# 2007/12/04 : pda/jean : specialization for postgresql 8523# 2010/12/29 : pda : i18n and netmagis merge 8524# 8525 8526proc pgauth-searchuser {dbfd _tabcrit {sort {+lastname +firstname}}} { 8527 upvar $_tabcrit tabcrit 8528 8529 # 8530 # Build-up the "where" clause 8531 # 8532 8533 set clauses {} 8534 set nwheres 0 8535 set from "" 8536 foreach c {login phlast phfirst lastname firstname addr mail phone mobile fax realm} { 8537 if {[info exists tabcrit($c)]} then { 8538 set re $tabcrit($c) 8539 if {$re ne ""} then { 8540 set re [::pgsql::quote $re] 8541 # quote SQL special characters 8542 regsub -all -- {%} $re {\\%} re 8543 regsub -all -- {_} $re {\\_} re 8544 # quote *our* special characters 8545 regsub -all -- {\*} $re {%} re 8546 regsub -all -- {\?} $re {_} re 8547 8548 if {$c eq "realm"} then { 8549 set from ", pgauth.member" 8550 set table "pgauth.member" 8551 lappend clauses "pgauth.user.login = member.login" 8552 } else { 8553 set table "pgauth.user" 8554 } 8555 8556 if {$c eq "phlast" || $c eq "phfirst"} then { 8557 lappend clauses "$table.$c = pgauth.soundex('$re')" 8558 } elseif {$c eq "realm"} then { 8559 set or {} 8560 foreach r $tabcrit(realm) { 8561 set qr [::pgsql::quote $r] 8562 lappend or "$table.realm = '$qr'" 8563 } 8564 if {[llength $or] > 0} then { 8565 set sor [join $or " OR "] 8566 lappend clauses "($sor)" 8567 } 8568 } else { 8569 # ILIKE = case insensitive LIKE 8570 lappend clauses "$table.$c ILIKE '$re'" 8571 } 8572 incr nwheres 8573 } 8574 } 8575 } 8576 if {$nwheres > 0} then { 8577 set where [join $clauses " AND "] 8578 set where "WHERE $where" 8579 } else { 8580 set where "" 8581 } 8582 8583 # 8584 # Build-up sort criterion 8585 # 8586 8587 set sqlsort {} 8588 set sqldistinct {} 8589 foreach t $sort { 8590 set way [string range $t 0 0] 8591 set col [string range $t 1 end] 8592 switch -- $way { 8593 - { set way "DESC" } 8594 + - 8595 default { set way "ASC" } 8596 } 8597 if {$col in {login lastname firstname mail phone addr mobile fax}} then { 8598 lappend sqlsort "pgauth.user.$col $way" 8599# XXX : I don't understand why I used this distinct clause 8600# lappend sqldistinct "pgauth.user.$col" 8601 } 8602 } 8603 if {[llength $sqlsort] == 0} then { 8604 set orderby "" 8605 } else { 8606 set orderby [join $sqlsort ", "] 8607 set orderby "ORDER BY $orderby" 8608 } 8609 8610 if {[llength $sqldistinct] == 0} then { 8611 set distinct "" 8612 } else { 8613 set distinct [join $sqldistinct ", "] 8614 set distinct "DISTINCT ON ($distinct)" 8615 } 8616 8617 # 8618 # Build the list of logins 8619 # 8620 8621 set lusers {} 8622 set sql "SELECT $distinct pgauth.user.login 8623 FROM pgauth.user $from 8624 $where 8625 $orderby" 8626 pg_select $dbfd $sql tab { 8627 lappend lusers $tab(login) 8628 } 8629 8630 return $lusers 8631} 8632 8633# 8634# Process password modification 8635# 8636# Input: 8637# - parameters : 8638# - dbfd : database handle 8639# - login : user login 8640# - action : list {action parameters} where: 8641# action = "block" (no parameter) 8642# action = "generate" (no parameter) 8643# action = "change" (parameters = password twice) 8644# - mail : {mail} or {nomail}, if the password must be sent by mail or not 8645# In the "mail" case, this parameter is a list 8646# {mail from replyto cc bcc subject body} 8647# - _newpw : in return, new password 8648# Output: 8649# - return value : empty string or error message 8650# 8651# History 8652# 2003/06/13 : pda/jean : design 8653# 2003/12/08 : pda : more complete "mail" parameter 8654# 2010/12/29 : pda : i18n and netmagis merge 8655# 8656 8657proc pgauth-chpw {dbfd login action mail _newpw} { 8658 upvar $_newpw newpw 8659 global libconf 8660 8661 if {! [pgauth-getuser $dbfd $login tab]} then { 8662 return [mc "Login '%s' does not exist" $login] 8663 } 8664 8665 switch -- [lindex $action 0] { 8666 block { 8667 set newpw [mc "<invalid>"] 8668 set tab(password) "*" 8669 } 8670 generate { 8671 set newpw [pgauth-genpw] 8672 set tab(password) [pgauth-crypt $newpw] 8673 } 8674 change { 8675 lassign $action c pw1 pw2 8676 8677 if {$pw1 ne $pw2} then { 8678 return [mc "Password mismatch"] 8679 } 8680 set newpw $pw1 8681 8682 if {[regexp {[\\'"`()]} $newpw]} then { 8683 return [mc "Invalid character in password"] 8684 } 8685 8686 set minpwlen [::dnsconfig get "authpgminpwlen"] 8687 set maxpwlen [::dnsconfig get "authpgmaxpwlen"] 8688 8689 if {[string length $newpw] < $minpwlen} then { 8690 return [mc "Password to short (< %s characters)" $minpwlen] 8691 } 8692 set newpw [string range $newpw 0 [expr $maxpwlen-1]] 8693 8694 set tab(password) [pgauth-crypt $newpw] 8695 } 8696 default { 8697 return [mc "Internal error: invalid 'action' value (%s)" $action] 8698 } 8699 } 8700 8701 if {[lindex $mail 0] eq "mail"} then { 8702 lassign $mail b from repl cc bcc subj body 8703 if {[::webapp::valid-email $tab(mail)]} then { 8704 set body [format $body $login $newpw] 8705 ::webapp::mail $from $repl $tab(mail) $cc $bcc $subj $body 8706 } else { 8707 return [mc "Invalid mail address, password is not modified"] 8708 } 8709 } 8710 8711 return [pgauth-setuser $dbfd tab] 8712} 8713 8714###################################### 8715# Pgsql realm management 8716###################################### 8717 8718# 8719# List existing realms 8720# 8721# Input: 8722# - parameters : 8723# - dbfd : database handle 8724# - tab : in return, array containing realm list 8725# tab(<realm>) {<descr> <list of users>} 8726# Output: 8727# - return value : (none) 8728# 8729# History 8730# 2003/05/30 : pda/jean : design 8731# 2007/12/04 : pda/jean : specialization for postgresql 8732# 2010/12/27 : pda : i18n and netmagis merge 8733# 8734 8735proc pgauth-lsrealm {dbfd _tab} { 8736 upvar $_tab tab 8737 8738 set sql "SELECT * FROM pgauth.realm" 8739 pg_select $dbfd $sql tabsql { 8740 set realm $tabsql(realm) 8741 set descr $tabsql(descr) 8742 set admin $tabsql(admin) 8743 set members {} 8744 set sqlm "SELECT login FROM pgauth.member WHERE realm = '$realm'" 8745 pg_select $dbfd $sqlm tabm { 8746 lappend members $tabm(login) 8747 } 8748 set tab($realm) [list $descr $members $admin] 8749 } 8750} 8751 8752# 8753# Add a PG realm into database 8754# 8755# Input: 8756# - parameters : 8757# - dbfd : database handle 8758# - realm : realm name 8759# - descr : realm description 8760# - admin : 0 or 1 8761# - _msg : in return, error message (if any) 8762# Output: 8763# - return value : 1 (ok) or 0 (error) 8764# - parameter _msg : error message if any 8765# 8766# History 8767# 2003/05/30 : pda/jean : design 8768# 2007/12/04 : pda/jean : specialization for postgresql 8769# 2010/12/27 : pda : i18n and netmagis merge 8770# 2011/01/07 : pda : add admin 8771# 8772 8773proc pgauth-addrealm {dbfd realm descr admin _msg} { 8774 upvar $_msg msg 8775 8776 set msg "" 8777 if {[regexp -- {^[a-z][-a-z0-9]*$} $realm]} then { 8778 set qrealm [::pgsql::quote $realm] 8779 set qdescr [::pgsql::quote $descr] 8780 set sql "INSERT INTO pgauth.realm (realm, descr, admin) 8781 VALUES ('$qrealm', '$qdescr', $admin)" 8782 if {! [::pgsql::execsql $dbfd $sql m]} then { 8783 set msg [mc {Unable to insert realm '%1$s': %2$s} $realm $m] 8784 } 8785 } else { 8786 set msg [mc {Invalid realm syntax (^[a-z][-a-z0-9]*$)}] 8787 } 8788 return [string equal $msg ""] 8789} 8790 8791# 8792# Remove a realm from database 8793# 8794# Input: 8795# - parameters : 8796# - dbfd : database handle 8797# - realm : realm name 8798# - _msg : in return, error message (if any) 8799# Output: 8800# - return value : 1 (ok) or 0 (error) 8801# - parameter _msg : error message if any 8802# 8803# Note : this function do not remove realms which have members 8804# (thanks to the SQL constraint) 8805# 8806# History 8807# 2003/05/30 : pda/jean : design 8808# 2007/12/04 : pda/jean : specialization for postgresql 8809# 2010/12/28 : pda : i18n and netmagis merge 8810# 8811 8812proc pgauth-delrealm {dbfd realm _msg} { 8813 upvar $_msg msg 8814 8815 set msg "" 8816 set qrealm [::pgsql::quote $realm] 8817 set sql "DELETE FROM pgauth.realm WHERE realm = '$qrealm'" 8818 if {! [::pgsql::execsql $dbfd $sql m]} then { 8819 set msg [mc {Unable to remove realm '%1$s': %2$s} $realm $m] 8820 } 8821 return [string equal $msg ""] 8822} 8823 8824# 8825# Modify a realm 8826# 8827# Input: 8828# - parameters : 8829# - dbfd : database handle 8830# - realm : realm name 8831# - descr : realm description 8832# - admin : 0 or 1 8833# - members : list of members 8834# - _msg : in return, error message (if any) 8835# Output: 8836# - return value : 1 (ok) or 0 (error) 8837# - parameter _msg : error message if any 8838# 8839# History 8840# 2003/06/04 : pda/jean : design 8841# 2007/12/04 : pda/jean : specialization for postgresql 8842# 2010/12/29 : pda : i18n and netmagis merge 8843# 2011/01/07 : pda : add admin 8844# 8845 8846proc pgauth-setrealm {dbfd realm descr admin members _msg} { 8847 upvar $_msg msg 8848 8849 set qrealm [::pgsql::quote $realm] 8850 8851 d dblock {} 8852 8853 # 8854 # If realm does not exists, create it. If it exists, modify description. 8855 # 8856 8857 set sql "SELECT realm FROM pgauth.realm WHERE realm = '$qrealm'" 8858 set found 0 8859 pg_select $dbfd $sql tab { 8860 set found 1 8861 } 8862 if {! $found} then { 8863 if {! [pgauth-addrealm $dbfd $realm $descr $admin msg]} then { 8864 d dbabort [mc "add %s" $realm] $msg 8865 } 8866 } else { 8867 set qdescr [::pgsql::quote $descr] 8868 set sql "UPDATE pgauth.realm 8869 SET descr = '$qdescr', admin = $admin 8870 WHERE realm = '$qrealm'" 8871 if {! [::pgsql::execsql $dbfd $sql m]} then { 8872 d dbabort [mc "modify %s" $realm] $msg 8873 } 8874 } 8875 8876 # 8877 # Remove member list 8878 # 8879 set sql "DELETE FROM pgauth.member WHERE realm = '$qrealm'" 8880 if {! [::pgsql::execsql $dbfd $sql m]} then { 8881 d dbabort [mc "modify %s" $realm] $msg 8882 } 8883 8884 # 8885 # Update member list 8886 # 8887 foreach login $members { 8888 set qlogin [::pgsql::quote $login] 8889 set sql "INSERT INTO pgauth.member (login, realm) 8890 VALUES ('$qlogin', '$qrealm')" 8891 if {! [::pgsql::execsql $dbfd $sql msg]} then { 8892 d dbabort [mc "add %s" "$login/$realm"] $msg 8893 } 8894 } 8895 8896 d dbcommit [mc "modify %s" $realm] 8897 8898 set msg "" 8899 return 1 8900} 8901 8902# 8903# Returns an HTML menu to select realms 8904# 8905# Input: 8906# - parameters : 8907# - dbfd : database handle 8908# - var : name of form variable 8909# - multiple : 1 if multiple choice, 0 if only one choice 8910# - realmsel : list of preselected realms (or empty list) 8911# Output: 8912# - return value : HTML code 8913# 8914# History 8915# 2003/06/03 : pda/jean : design 8916# 2003/06/13 : pda/jean : add parameter realmsel 8917# 2003/06/27 : pda : package 8918# 2010/12/28 : pda : i18n and netmagis merge 8919# 8920 8921proc pgauth-htmlrealmmenu {dbfd var multiple realmsel} { 8922 # 8923 # Index pre-selected realms 8924 # 8925 foreach r $realmsel { 8926 set tabsel($r) "" 8927 } 8928 8929 # 8930 # Get realm list 8931 # 8932 pgauth-lsrealm $dbfd tabrlm 8933 8934 # 8935 # Build key/value list for the menu 8936 # 8937 8938 set l {} 8939 set lsel {} 8940 set idx 0 8941 foreach r [lsort [array names tabrlm]] { 8942 lappend l [list $r $r] 8943 if {[info exists tabsel($r)]} then { 8944 lappend lsel $idx 8945 } 8946 incr idx 8947 } 8948 8949 # 8950 # Multiple choices? 8951 # 8952 8953 if {$multiple} then { 8954 set size [llength [array names tabrlm]] 8955 } else { 8956 set size 1 8957 } 8958 8959 return [::webapp::form-menu $var $size $multiple $l $lsel] 8960} 8961 8962###################################### 8963# HTML account management 8964###################################### 8965 8966# 8967# Heart of CGI script for applications which manage users. 8968# 8969# Input: 8970# - parameters : 8971# - ae : auth execution environment of the script, as an indexed array: 8972# dbfd : access to auth database 8973# url : url of CGI script 8974# realms : realms where application user can belong to. 8975# If realms = {}, we can access every realm 8976# If only one realm, realm list is not displayed when 8977# adding a user 8978# maxrealms : maximum number of realms displayed in the listbox 8979# or 0 to use exact number of displayed realms 8980# page-* : HTML/LaTeX templates 8981# -index : index of different actions 8982# -ok : action done 8983# -add1 : first page of user add 8984# -choice : choice of user, if more than one found 8985# -mod : parameter modification 8986# -del : confirm user removal 8987# -passwd : actions on user password 8988# -list : list of users 8989# -listtex : list of users in latex format 8990# -sel : user selection with criterion 8991# specif : application specific user data 8992# {{<title> <type>} ...} 8993# (see ::webapp::form-field for type) 8994# script-* : scripts to execute to access and display user 8995# characteristics, specific to an application: 8996# - getuser : display user information and returns a 8997# list {value ...} in the same order than 8998# in "specif" list 8999# - deluser : remove user from application 9000# - setuser : add or modify user in application 9001# - chkuser : check if a user modification is authorized 9002# mailfrom : mail header in case of password generation 9003# mailreplyto : mail header in case of password generation 9004# mailcc : mail header in case of password generation 9005# mailbcc : mail header in case of password generation 9006# mailsubject : mail header in case of password generation 9007# mailbody : mail header in case of password generation 9008# - ftab : form tab 9009# Output: 9010# - return value : (none) 9011# - stdout : an HTML page 9012# 9013# History 9014# 2003/07/29 : pda : design 9015# 2003/07/31 : pda/jean : done 9016# 2003/12/14 : pda : add mail* 9017# 2010/12/29 : pda : i18n and netmagis merge 9018# 2011/01/07 : pda : add ftab array 9019# 9020 9021proc pgauth-accmanage {_ae _ftab} { 9022 upvar $_ae ae 9023 upvar $_ftab ftab 9024 9025 set form { 9026 {action 0 1} 9027 {state 0 1} 9028 } 9029 pgauth-get-data ftab $form 9030 ::webapp::import-vars ftab $form 9031 9032 switch -- $action { 9033 add { set l [pgauth-ac-add ae ftab $state] } 9034 list - 9035 print { set l [pgauth-ac-consprn ae ftab $state $action] } 9036 del - 9037 mod - 9038 passwd { set l [pgauth-ac-delmodpwd ae ftab $state $action] } 9039 default { set l [pgauth-ac-nothing ae ftab $state] } 9040 } 9041 lassign $l format page lsubst 9042 9043 lappend lsubst [list %ACTION% $action] 9044 d urlset "%URLFORM%" $ae(url) {} 9045 d result $page $lsubst 9046 exit 0 9047} 9048 9049proc pgauth-get-data {_ftab form} { 9050 upvar $_ftab ftab 9051 9052 if {[llength [::webapp::get-data ftab $form]] != [llength $form]} then { 9053 d error [mc "Invalid input '%s'" $ftab(_error)] 9054 } 9055} 9056 9057proc pgauth-ac-nothing {_ae _ftab state} { 9058 upvar $_ae ae 9059 upvar $_ftab ftab 9060 9061 return [list "html" $ae(page-index) {}] 9062} 9063 9064proc pgauth-ac-add {_ae _ftab state} { 9065 upvar $_ae ae 9066 upvar $_ftab ftab 9067 9068 set lsubst {} 9069 switch -- $state { 9070 name { 9071 # 9072 # User name has been introduced. Search this name. 9073 # 9074 set form { 9075 {lastname 1 1} 9076 } 9077 pgauth-get-data ftab $form 9078 9079 set lastname [lindex $ftab(lastname) 0] 9080 set tabcrit(phlast) $lastname 9081 set lusers [pgauth-searchuser $ae(dbfd) tabcrit {+lastname +firstname}] 9082 set nbut [llength $lusers] 9083 9084 if {$nbut > 0} then { 9085 # 9086 # Some users match this name. 9087 # 9088 # %ACTION% 9089 # %MESSAGE% 9090 # %LISTUSERS% 9091 # %NONE% 9092 # 9093 set qlast [::webapp::html-string $lastname] 9094 set message [mc "Some accounts match '%s'. Choose one, or ask for a new account" $qlast] 9095 lappend lsubst [list %MESSAGE% $message] 9096 9097 lappend lsubst [list %LISTUSERS% \ 9098 [pgauth-ac-display-choice ae $lusers "ajout"] \ 9099 ] 9100 9101 d urlset "" $ae(url) [list {action add} \ 9102 {state nouveau} \ 9103 [list "lastname" $lastname] \ 9104 ] 9105 set url [d urlget ""] 9106 set aucun [::webapp::helem "form" \ 9107 [::webapp::form-submit {} [mc "Create a new account"]] 9108 "method" "post" "action" $url] 9109 lappend lsubst [list %NONE% $aucun] 9110 9111 set page $ae(page-choice) 9112 } else { 9113 # 9114 # No user match. Prepare the form to add a new user. 9115 # 9116 # %ACTION% 9117 # %STATE% 9118 # %LOGIN% 9119 # %PARAMUSER% 9120 # %TITLE% 9121 # 9122 set lsubst [pgauth-ac-display-mod ae "_new" $lastname] 9123 set page $ae(page-mod) 9124 } 9125 } 9126 morethanone { 9127 # 9128 # One user selected. Prepare form to input user modifications. 9129 # 9130 # %ACTION% 9131 # %STATE% 9132 # %LOGIN% 9133 # %PARAMUSER% 9134 # %TITLE% 9135 # 9136 set form { 9137 {login 1 1} 9138 } 9139 pgauth-get-data ftab $form 9140 9141 set login [lindex $ftab(login) 0] 9142 set lsubst [pgauth-ac-display-mod ae $login ""] 9143 set page $ae(page-mod) 9144 } 9145 nouveau { 9146 # 9147 # User addition required. Prepare form to input a new user. 9148 # 9149 # %ACTION% 9150 # %LOGIN% 9151 # %PARAMUSER% 9152 # 9153 set form { 9154 {lastname 0 1} 9155 } 9156 pgauth-get-data ftab $form 9157 9158 set lastname [lindex $ftab(lastname) 0] 9159 9160 set lsubst [pgauth-ac-display-mod ae "_new" $lastname] 9161 set page $ae(page-mod) 9162 } 9163 creation { 9164 # 9165 # New user data is given. Create user, and give control 9166 # to the password modification page. 9167 # 9168 # %ACTION% (passwd) 9169 # %LOGIN% 9170 # 9171 set form { 9172 {login 1 1} 9173 } 9174 pgauth-get-data ftab $form 9175 9176 set login [lindex $ftab(login) 0] 9177 if {[pgauth-getuser $ae(dbfd) $login u]} then { 9178 d error [mc "Login '%s' already exists" $login] 9179 } 9180 9181 # 9182 # New user. Ignore supplementary and give control to 9183 # the password modification page. 9184 # 9185 pgauth-ac-store-mod ae ftab $login 9186 9187 set lsubst [concat $lsubst [pgauth-ac-display-passwd ae $login]] 9188 set page $ae(page-passwd) 9189 } 9190 ok { 9191 # 9192 # Store modification of an existing user. 9193 # 9194 # %TITLEACTION% (ajout) 9195 # %COMPLEMENT% 9196 # 9197 set form { 9198 {login 1 1} 9199 } 9200 pgauth-get-data ftab $form 9201 9202 set login [lindex $ftab(login) 0] 9203 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 9204 d error [mc "Login '%s' does not exist" $login] 9205 } 9206 9207 # 9208 # Existing user in database 9209 # 9210 set lsubst [pgauth-ac-store-mod ae ftab $login] 9211 set page $ae(page-ok) 9212 } 9213 default { 9214 set page $ae(page-add1) 9215 } 9216 } 9217 return [list "html" $page $lsubst] 9218} 9219 9220proc pgauth-ac-consprn {_ae _ftab state mode} { 9221 upvar $_ae ae 9222 upvar $_ftab ftab 9223 global libconf 9224 9225 set lsubst {} 9226 set format "html" 9227 switch -- $state { 9228 criteres { 9229 # 9230 # Criterion is given 9231 # 9232 # %NBUSERS% 9233 # %S% 9234 # %DATE% 9235 # %HEURE% 9236 # %TABLEAU% 9237 # 9238 9239 set lusers [pgauth-ac-search-crit ae ftab] 9240 if {[llength $lusers] == 0} then { 9241 # 9242 # No user found. Display again the criterion selection page. 9243 # 9244 set lsubst [pgauth-ac-display-crit ae ftab [mc "No account found"]] 9245 set page $ae(page-sel) 9246 } else { 9247 # 9248 # Guess output format 9249 # 9250 9251 switch $mode { 9252 list { 9253 set tabfmt "html" 9254 set page $ae(page-list) 9255 } 9256 print { 9257 set format "pdf" 9258 set tabfmt "latex" 9259 set page $ae(page-listtex) 9260 } 9261 } 9262 9263 # 9264 # Display user list 9265 # 9266 9267 set lines {} 9268 lappend lines [list "Title" \ 9269 [mc "Login"] \ 9270 [mc "Name"] \ 9271 [mc "Address"] \ 9272 [mc "Mail"] \ 9273 [mc "Phone"] \ 9274 [mc "Fax"] \ 9275 [mc "Mobile"] \ 9276 [mc "Realms"] \ 9277 ] 9278 foreach login $lusers { 9279 if {[pgauth-getuser $ae(dbfd) $login tab]} then { 9280 set myrealms [pgauth-ac-my-realms ae $tab(realms)] 9281 lappend lines [list "User" \ 9282 $tab(login) \ 9283 "$tab(lastname) $tab(firstname)" \ 9284 $tab(addr) \ 9285 $tab(mail) \ 9286 $tab(phone) $tab(fax) $tab(mobile) \ 9287 $myrealms 9288 ] \ 9289 } 9290 } 9291 set tableau [::arrgen::output $tabfmt $libconf(tabulist) $lines] 9292 9293 # 9294 # Time 9295 # 9296 9297 set date [clock format [clock seconds] -format "%d/%m/%Y"] 9298 set heure [clock format [clock seconds] -format "%Hh%M"] 9299 9300 lappend lsubst [list %TABLEAU% $tableau] 9301 lappend lsubst [list %NBUSERS% [llength $lusers]] 9302 lappend lsubst [list %DATE% $date] 9303 lappend lsubst [list %HEURE% $heure] 9304 } 9305 } 9306 default { 9307 # 9308 # Initial page to select criteria 9309 # 9310 # %ACTION% 9311 # %MESSAGE% 9312 # %CRITERES% 9313 # 9314 set lsubst [pgauth-ac-display-crit ae ftab ""] 9315 set page $ae(page-sel) 9316 } 9317 } 9318 return [list $format $page $lsubst] 9319} 9320 9321proc pgauth-ac-delmodpwd {_ae _ftab state action} { 9322 upvar $_ae ae 9323 upvar $_ftab ftab 9324 9325 switch -- $state { 9326 criteres { 9327 # 9328 # Criterion was given 9329 # 9330 # %LOGIN% 9331 # %LASTNAME% 9332 # %FIRSTNAME% 9333 # 9334 9335 set lusers [pgauth-ac-search-crit ae ftab] 9336 switch [llength $lusers] { 9337 0 { 9338 # 9339 # No user found 9340 # 9341 set lsubst [pgauth-ac-display-crit ae ftab [mc "No account found"]] 9342 set page $ae(page-sel) 9343 } 9344 1 { 9345 # 9346 # Display page to remove, modify or change password 9347 # of an user 9348 # 9349 set login [lindex $lusers 0] 9350 switch -- $action { 9351 del { 9352 set lsubst [pgauth-ac-display-del ae $login] 9353 set page $ae(page-del) 9354 } 9355 mod { 9356 set lsubst [pgauth-ac-display-mod ae $login ""] 9357 set page $ae(page-mod) 9358 } 9359 passwd { 9360 set lsubst [pgauth-ac-display-passwd ae $login] 9361 set page $ae(page-passwd) 9362 } 9363 default { 9364 d error [mc "Invalid input"] 9365 } 9366 } 9367 } 9368 default { 9369 # 9370 # Some users match. 9371 # 9372 # %ACTION% 9373 # %MESSAGE% 9374 # %LISTUSERS% 9375 # %NONE% 9376 # 9377 set message [mc "Some accounts match criteria. Choose one"] 9378 lappend lsubst [list %MESSAGE% $message] 9379 9380 lappend lsubst [list %LISTUSERS% \ 9381 [pgauth-ac-display-choice ae $lusers $action] \ 9382 ] 9383 9384 lappend lsubst [list %NONE% ""] 9385 set page $ae(page-choice) 9386 } 9387 } 9388 } 9389 morethanone { 9390 # 9391 # Display page to remove, modify or change password of an user 9392 # 9393 set form { 9394 {login 1 1} 9395 } 9396 pgauth-get-data ftab $form 9397 9398 set login [lindex $ftab(login) 0] 9399 9400 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 9401 d error [mc "Login '%s' does not exist" $login] 9402 } 9403 9404 switch -- $action { 9405 del { 9406 set lsubst [pgauth-ac-display-del ae $login] 9407 set page $ae(page-del) 9408 } 9409 mod { 9410 set lsubst [pgauth-ac-display-mod ae $login ""] 9411 set page $ae(page-mod) 9412 } 9413 passwd { 9414 set lsubst [pgauth-ac-display-passwd ae $login] 9415 set page $ae(page-passwd) 9416 } 9417 default { 9418 d error [mc "Invalid input"] 9419 } 9420 } 9421 9422 } 9423 ok { 9424 # 9425 # Perform action 9426 # 9427 9428 set form { 9429 {login 1 1} 9430 } 9431 pgauth-get-data ftab $form 9432 9433 set login [lindex $ftab(login) 0] 9434 9435 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 9436 d error [mc "Login '%s' does not exist" $login] 9437 } 9438 9439 set page $ae(page-ok) 9440 switch -- $action { 9441 del { 9442 set lsubst [pgauth-ac-del-user ae ftab $login] 9443 } 9444 mod { 9445 set lsubst [pgauth-ac-store-mod ae ftab $login] 9446 } 9447 passwd { 9448 set lsubst [pgauth-ac-store-passwd ae ftab $login] 9449 } 9450 default { 9451 d error [mc "Invalid input"] 9452 } 9453 } 9454 } 9455 default { 9456 # 9457 # Initial page for criteria 9458 # 9459 # %ACTION% 9460 # %MESSAGE% 9461 # %CRITERES% 9462 # 9463 set lsubst [pgauth-ac-display-crit ae ftab ""] 9464 set page $ae(page-sel) 9465 } 9466 } 9467 9468 return [list "html" $page $lsubst] 9469} 9470 9471# 9472# Utility functions for pgauth-accmanage 9473# 9474 9475# 9476# Returns a realm list, extract from "realms" , where only authorized 9477# realms (i.ae. those in ae(realms)) are displayed. If ae(realms) is 9478# empty, all realms may be displayed. 9479# 9480 9481proc pgauth-ac-my-realms {_ae realms} { 9482 upvar $_ae ae 9483 9484 if {[llength $ae(realms)] == 0} then { 9485 set rr $realms 9486 } else { 9487 foreach r $ae(realms) { 9488 set x($r) 0 9489 } 9490 set rr {} 9491 foreach r $realms { 9492 if {[info exists x($r)]} then { 9493 lappend rr $r 9494 } 9495 } 9496 } 9497 return $rr 9498} 9499 9500# 9501# Returns a list of users with associated URLs 9502# 9503# Return : value for %LISTUSERS% 9504# 9505 9506proc pgauth-ac-display-choice {_ae lusers action} { 9507 upvar $_ae ae 9508 global libconf 9509 9510 set lines {} 9511 lappend lines [list "Title" \ 9512 [mc "Login"] \ 9513 [mc "Name"] \ 9514 [mc "Address"] \ 9515 [mc "Mail"] \ 9516 [mc "Realms"] \ 9517 ] 9518 foreach login $lusers { 9519 if {[pgauth-getuser $ae(dbfd) $login tab]} then { 9520 set hlogin [::webapp::html-string $login] 9521 d urlset "" $ae(url) [list [list "action" $action] \ 9522 {state morethanone} \ 9523 [list "login" $login] \ 9524 ] 9525 set url [d urlget ""] 9526 set urllogin [::webapp::helem "a" $hlogin "href" $url] 9527 set myrealms [pgauth-ac-my-realms ae $tab(realms)] 9528 lappend lines [list "User" \ 9529 $urllogin "$tab(lastname) $tab(firstname)" \ 9530 $tab(addr) $tab(mail) $myrealms 9531 ] 9532 } 9533 } 9534 return [::arrgen::output "html" $libconf(tabuchoice) $lines] 9535} 9536 9537# 9538# Returns a form part to input user information 9539# 9540# Retour : values for %LOGIN%, %PARAMUSER%, %STATE% and %TITLE% 9541# 9542 9543proc pgauth-ac-display-mod {_ae login lastname} { 9544 upvar $_ae ae 9545 global libconf 9546 9547 # 9548 # Get auth data for user, or simulate them if this is a creation 9549 # 9550 9551 set new [string equal $login "_new"] 9552 if {$new} then { 9553 array set u { 9554 login {} 9555 lastname {} 9556 firstname {} 9557 addr {} 9558 mail {} 9559 phone {} 9560 fax {} 9561 mobile {} 9562 realms {} 9563 } 9564 set u(lastname) $lastname 9565 set state "creation" 9566 set title [mc "Creation"] 9567 } else { 9568 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 9569 d error [mc "Login '%s' does not exist" $login] 9570 } 9571 set state "ok" 9572 set title [mc "Modification"] 9573 } 9574 9575 # 9576 # Realm edition choice 9577 # 9578 9579 set menurealms [pgauth-build-realm-index $ae(dbfd) "list" \ 9580 0 $ae(realms) $ae(maxrealms) gidx] 9581 9582 # 9583 # Get existing values, or default values for a new user 9584 # 9585 9586 set valu [uplevel 3 [format $ae(script-getuser) $login]] 9587 9588 # 9589 # Input fields for user 9590 # 9591 9592 set lines {} 9593 9594 foreach c [concat $libconf(editfields) $libconf(editrealms)] { 9595 lassign $c ctitle spec var user 9596 if {$var eq "login" && ! $new} then { 9597 # 9598 # Special case for "login" field if editable 9599 # 9600 set t [::webapp::html-string $login] 9601 append t [::webapp::form-hidden "login" $login] 9602 } elseif {$var eq "realms"} then { 9603 # 9604 # Special case for realms 9605 # 9606 if {[llength $menurealms] == 0} then { 9607 set t "" 9608 } else { 9609 set lidx {} 9610 foreach r $u(realms) { 9611 if {[info exists gidx($r)]} then { 9612 lappend lidx $gidx($r) 9613 } 9614 } 9615 set t [::webapp::form-field $menurealms $var $lidx] 9616 } 9617 } elseif {$user} then { 9618 # 9619 # General case : a field to modify 9620 # 9621 if {[lindex $spec 0] eq "yesno"} then { 9622 set spec [list "yesno" [mc [lindex $spec 1]]] 9623 } 9624 set t [::webapp::form-field $spec $var $u($var)] 9625 } else { 9626 # 9627 # Else, it is only a field for search (eg: phlast/phfirst) 9628 # 9629 set t "" 9630 } 9631 9632 if {$t ne ""} then { 9633 set l [list Normal [mc $ctitle] $t] 9634 lappend lines $l 9635 } 9636 } 9637 9638 # 9639 # Generate input field specific to the application 9640 # 9641 9642 set n 0 9643 foreach c $ae(specif) v $valu { 9644 lassign $c ctitle spec 9645 incr n 9646 set var "uvar$n" 9647 lappend lines [list "Normal" $ctitle [::webapp::form-field $spec $var $v]] 9648 } 9649 9650 set paramutilisateur [::arrgen::output html $libconf(tabumod) $lines] 9651 9652 # 9653 # Substitution lists 9654 # 9655 9656 lappend lsubst [list %LOGIN% $login] 9657 lappend lsubst [list %PARAMUSER% $paramutilisateur] 9658 lappend lsubst [list %STATE% $state] 9659 lappend lsubst [list %TITLE% $title] 9660 9661 return $lsubst 9662} 9663 9664# 9665# Store user information (new or modification) 9666# 9667# Return : values for %TITLEACTION% and %COMPLEMENT% 9668# 9669 9670proc pgauth-ac-store-mod {_ae _ftab login} { 9671 upvar $_ae ae 9672 upvar $_ftab ftab 9673 global libconf 9674 9675 # 9676 # Check if the script is authorized to modify user 9677 # 9678 set msg [uplevel 3 [format $ae(script-chkuser) $login]] 9679 if {$msg ne ""} then { 9680 d error [mc {Unable to modify '%1$s': %2$s} $login $msg] 9681 } 9682 9683 # 9684 # Extract field values 9685 # 9686 9687 set form [pgauth-build-form-spec "mod" \ 9688 [concat $libconf(editfields) $libconf(editrealms)] \ 9689 $ae(specif) \ 9690 ] 9691 pgauth-get-data ftab $form 9692 9693 # 9694 # Get existing data from database 9695 # 9696 set u(realms) {} 9697 set new [expr ! [pgauth-getuser $ae(dbfd) $login u]] 9698 9699 d dblock {pgauth.user pgauth.member} 9700 9701 # 9702 # Set user data. Realms will be set after. 9703 # 9704 foreach c $libconf(editfields) { 9705 lassign $c title spec var user 9706 if {$user} then { 9707 set u($var) [lindex $ftab($var) 0] 9708 } 9709 } 9710 9711 # 9712 # Realm management 9713 # - if ae(realms) is empty 9714 # authorize all specific realms in form 9715 # - if ae(realms) contains only one element 9716 # do not use form data, and add realm in database 9717 # - lif ae(realms) contains more than one element 9718 # use form data, and set all realms present in ae(realms) 9719 # 9720 pgauth-lsrealm $ae(dbfd) tabrlm 9721 switch [llength $ae(realms)] { 9722 0 { 9723 foreach r $ftab(realms) { 9724 if {! [info exists tabrlm($r)]} then { 9725 d error [mc "Invalid realm '%s'" $r] 9726 } 9727 } 9728 set u(realms) $ftab(realms) 9729 } 9730 1 { 9731 set found 0 9732 set er [lindex $ae(realms) 0] 9733 foreach r $u(realms) { 9734 if {$r eq $er} then { 9735 set found 1 9736 break 9737 } 9738 } 9739 if {! $found} then { 9740 lappend u(realms) $er 9741 } 9742 } 9743 default { 9744 foreach r $ae(realms) { 9745 set ar($r) 1 9746 } 9747 9748 # nr = u realms, minus realms from ae(realms) 9749 set nr {} 9750 foreach r $u(realms) { 9751 if {! [info exists ar($r)]} then { 9752 lappend nr $r 9753 } 9754 } 9755 set u(realms) $nr 9756 9757 # add form realms, if they are also in ar() 9758 foreach r $ftab(realms) { 9759 if {! [info exists tabrlm($r)]} then { 9760 d error [mc "Invalid realm '%s'" $r] 9761 } 9762 if {[info exists ar($r)]} then { 9763 lappend u(realms) $r 9764 } 9765 } 9766 } 9767 } 9768 9769 # 9770 # Store user in database 9771 # 9772 set msg [pgauth-setuser $ae(dbfd) u "no transaction"] 9773 if {$msg ne ""} then { 9774 d dbabort [mc "add %s" $login] $msg 9775 } 9776 9777 9778 # 9779 # Store application specific data 9780 # 9781 set lval {} 9782 set i 1 9783 while {[info exists ftab(uvar$i)]} { 9784 lappend lval $ftab(uvar$i) 9785 incr i 9786 } 9787 9788 set msg [uplevel 3 [format $ae(script-setuser) $login $lval]] 9789 if {$msg ne ""} then { 9790 d dbabort [mc "add %s" $login] $msg 9791 } 9792 9793 # 9794 # C'est fini, on y va ! 9795 # 9796 d dbcommit [mc "add %s" $login] 9797 9798 if {$new} then { 9799 set title [mc "Account '%s' insertion" $login] 9800 } else { 9801 set title [mc "Account '%s' modification" $login] 9802 } 9803 9804 set lsubst {} 9805 lappend lsubst [list %TITLEACTION% $title] 9806 lappend lsubst [list %COMPLEMENT% ""] 9807 return $lsubst 9808} 9809 9810# 9811# Display search criterion 9812# 9813# Return : values for %CRITERES% and %MESSAGE% 9814# 9815 9816proc pgauth-ac-display-crit {_ae _ftab msg} { 9817 upvar $_ae ae 9818 upvar $_ftab ftab 9819 global libconf 9820 9821 # 9822 # Realm management 9823 # 9824 9825 set menurealms [pgauth-build-realm-index $ae(dbfd) "menu" 1 $ae(realms) 1 {}] 9826 if {[llength $menurealms] == 0} then { 9827 set menurealms {hidden} 9828 } 9829 9830 # 9831 # Generate input form 9832 # 9833 9834 set lines {} 9835 foreach c [concat $libconf(editfields) $libconf(editrealms)] { 9836 lassign $c title spec var user 9837 if {$var eq "realms"} then { 9838 set t [::webapp::form-field $menurealms $var ""] 9839 } else { 9840 if {[lindex $spec 0] eq "yesno"} then { 9841 set spec [list "yesno" [mc [lindex $spec 1]]] 9842 } 9843 set t [::webapp::form-field $spec $var ""] 9844 } 9845 9846 set l [list "Normal" [mc $title] $t] 9847 lappend lines $l 9848 } 9849 set crit [::arrgen::output html $libconf(tabumod) $lines] 9850 9851 set lsubst {} 9852 lappend lsubst [list %CRITERES% $crit] 9853 lappend lsubst [list %MESSAGE% $msg] 9854 9855 return $lsubst 9856} 9857 9858# 9859# Exploit search criterion to return a list of users 9860# 9861# Return : list of found logins 9862# 9863 9864proc pgauth-ac-search-crit {_ae _ftab} { 9865 upvar $_ae ae 9866 upvar $_ftab ftab 9867 global libconf 9868 9869 # 9870 # Get parameters 9871 # 9872 9873 set form [pgauth-build-form-spec "crit" \ 9874 [concat $libconf(editfields) $libconf(editrealms)] \ 9875 {} \ 9876 ] 9877 pgauth-get-data ftab $form 9878 9879 foreach f $form { 9880 set var [lindex $f 0] 9881 set $var [string trim [lindex $ftab($var) 0]] 9882 } 9883 9884 # 9885 # If no clause is specified, return an appropriate message (without 9886 # returning all users, which could be long). 9887 # If we really want all users, one must explicit this by using the 9888 # "*" special character in a criterion. 9889 # 9890 9891 set ncrit 0 9892 foreach var {login lastname firstname mail addr realms} { 9893 if {[set $var] ne ""} then { 9894 incr ncrit 9895 } 9896 } 9897 9898 set allrealms 1 9899 if {! ($realms eq "_" || $realms eq "")} then { 9900 set allrealms 0 9901 incr ncrit 9902 } 9903 9904 if {$ncrit == 0} then { 9905 d error [mc "You did not specify any criterion"] 9906 } 9907 9908 # 9909 # Use phonetic search 9910 # 9911 9912 if {[regexp {^[01]$} $phren] && $phren} then { 9913 set phlast "" 9914 } else { 9915 set phlast $lastname 9916 set lastname "" 9917 } 9918 9919 if {[regexp {^[01]$} $phrep] && $phrep} then { 9920 set phfirst "" 9921 } else { 9922 set phfirst $firstname 9923 set firstname "" 9924 } 9925 9926 # 9927 # Search with specified criterion 9928 # 9929 # Special case for realms: we search for the specified realm, or 9930 # all realms (those defined, or those found in database) is nothing 9931 # is specified. 9932 # 9933 9934 foreach var {login lastname firstname phlast phfirst mail addr} { 9935 set tabcrit($var) [set $var] 9936 } 9937 9938 if {$allrealms} then { 9939 if {[llength $ae(realms)] > 0} then { 9940 set tabcrit(realm) $ae(realms) 9941 } 9942 } else { 9943 set lr $ae(realms) 9944 if {[llength $lr] == 0} then { 9945 pgauth-lsrealm $ae(dbfd) tabrlm 9946 set lr [array names tabrlm] 9947 } 9948 if {[lsearch -exact $lr $realms] == -1} then { 9949 d error [mc "Realm '%s' not found" $realms] 9950 } 9951 set tabcrit(realm) $realms 9952 } 9953 9954 return [pgauth-searchuser $ae(dbfd) tabcrit {+lastname +firstname}] 9955} 9956 9957# 9958# Display possible actions for a password change 9959# 9960# Return : values for %LOGIN%, %LASTNAME% and %FIRSTNAME%. 9961# 9962 9963proc pgauth-ac-display-passwd {_ae login} { 9964 upvar $_ae ae 9965 9966 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 9967 d error [mc "Login '%s' does not exist" $login] 9968 } 9969 9970 set login [::webapp::html-string $login] 9971 set lastname [::webapp::html-string $u(lastname)] 9972 set firstname [::webapp::html-string $u(firstname)] 9973 9974 set minpwlen [::dnsconfig get "authpgminpwlen"] 9975 set maxpwlen [::dnsconfig get "authpgmaxpwlen"] 9976 9977 set lsubst {} 9978 lappend lsubst [list %LOGIN% $login] 9979 lappend lsubst [list %LASTNAME% $lastname] 9980 lappend lsubst [list %FIRSTNAME% $firstname] 9981 lappend lsubst [list %MINPWLEN% $minpwlen] 9982 lappend lsubst [list %MAXPWLEN% $maxpwlen] 9983 9984 return $lsubst 9985} 9986 9987# 9988# Store a password 9989# 9990# Return : values for %TITLEACTION% and %COMPLEMENT% 9991# 9992 9993proc pgauth-ac-store-passwd {_ae _ftab login} { 9994 upvar $_ae ae 9995 upvar $_ftab ftab 9996 9997 # 9998 # Check if the script is authorized to modify user 9999 # 10000 set msg [uplevel 3 [format $ae(script-chkuser) $login]] 10001 if {$msg ne ""} then { 10002 d error [mc {Unable to change password of '%1$s': %2$s} $login $msg] 10003 } 10004 10005 # 10006 # Get form values 10007 # 10008 set form { 10009 {pw1 0 1} 10010 {pw2 0 1} 10011 {block 0 1} 10012 {gen 0 1} 10013 {change 0 1} 10014 } 10015 10016 pgauth-get-data ftab $form 10017 ::webapp::import-vars ftab $form 10018 10019 set hlogin [::webapp::html-string $login] 10020 10021 if {$block ne ""} then { 10022 set msg [pgauth-chpw $ae(dbfd) $login {block} "nomail" {}] 10023 set res [mc "Block account '%s'" $hlogin] 10024 set comp "" 10025 } elseif {$gen ne ""} then { 10026 set mail [list "mail" $ae(mailfrom) $ae(mailreplyto) \ 10027 $ae(mailcc) $ae(mailbcc) \ 10028 [encoding convertto iso8859-1 $ae(mailsubject)] \ 10029 [encoding convertto iso8859-1 $ae(mailbody)]] 10030 set msg [pgauth-chpw $ae(dbfd) $login {generate} $mail newpw] 10031 set res [mc {Password generation (%1$s) for %2$s} $newpw $hlogin] 10032 set comp [mc "Password has been sent by mail"] 10033 } elseif {$change ne ""} then { 10034 set pw1 [lindex $ftab(pw1) 0] 10035 set pw2 [lindex $ftab(pw2) 0] 10036 set msg [pgauth-chpw $ae(dbfd) $login [list "change" $pw1 $pw2] "nomail" {}] 10037 set res [mc "Password change for '%s'" $hlogin] 10038 set comp "" 10039 } else { 10040 d error [mc "Invalid input"] 10041 } 10042 10043 if {$msg ne ""} then { 10044 d error $msg 10045 } 10046 10047 # 10048 # Display result 10049 # 10050 10051 set lsubst {} 10052 lappend lsubst [list %TITLEACTION% $res] 10053 lappend lsubst [list %COMPLEMENT% $comp] 10054 10055 return $lsubst 10056} 10057 10058# 10059# Display removal confirmation page 10060# 10061# Return : value for %USER% 10062# 10063 10064proc pgauth-ac-display-del {_ae login} { 10065 upvar $_ae ae 10066 10067 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 10068 return [mc "Login '%s' does not exist" $login] 10069 } 10070 10071 set lsubst {} 10072 lappend lsubst [list %USER% $login] 10073 lappend lsubst [list %LOGIN% [::webapp::html-string $login]] 10074 return $lsubst 10075} 10076 10077# 10078# Remove user 10079# 10080# Return : values for %TITLEACTION% and %COMPLEMENT% 10081# 10082 10083proc pgauth-ac-del-user {_ae _ftab login} { 10084 upvar $_ae ae 10085 upvar $_ftab ftab 10086 10087 # 10088 # Default messages 10089 # 10090 set msg [mc "Remove '%s' from application" $login] 10091 set comp [mc "Account is still active in authentication subsystem"] 10092 10093 # 10094 # Check if the script is authorized to modify user 10095 # 10096 set msg [uplevel 3 [format $ae(script-chkuser) $login]] 10097 if {$msg ne ""} then { 10098 d error [mc {Unable to modify '%1$s': %2$s} $login $msg] 10099 } 10100 10101 # 10102 # Remove rights on application 10103 # 10104 set msg [uplevel 3 [format $ae(script-deluser) $login]] 10105 if {$msg ne ""} then { 10106 d error $msg 10107 } 10108 10109 # 10110 # Remove from realms 10111 # 10112 if {! [pgauth-getuser $ae(dbfd) $login u]} then { 10113 set comp [mc "Login '%s' does not exist" $login] 10114 } else { 10115 set rmr {} 10116 set nr {} 10117 foreach r $u(realms) { 10118 if {[lsearch -exact $ae(realms) $r] == -1} then { 10119 # realm is not one of the realms to remove 10120 lappend nr $r 10121 } else { 10122 # realm to remove 10123 lappend rmr $r 10124 } 10125 } 10126 if {[llength $nr] != [llength $u(realms)]} then { 10127 set u(realms) $nr 10128 set m [pgauth-setuser $ae(dbfd) u] 10129 if {$m eq ""} then { 10130 set rmr [join $rmr ", "] 10131 set comp [mc "Account has been removed from realms: %s" $rmr] 10132 } else { 10133 set comp [mc {Error while removing realms %1$s: %2$s} $rmr $m] 10134 } 10135 } 10136 } 10137 10138 set lsubst {} 10139 lappend lsubst [list %TITLEACTION% [::webapp::html-string $msg]] 10140 lappend lsubst [list %COMPLEMENT% [::webapp::html-string $comp]] 10141 return $lsubst 10142} 10143 10144# 10145# Build a form spec 10146# 10147# Input: 10148# - modif : "mod" or "crit" 10149# - spec1 : see variable libconf(editfields) 10150# - spec2 : see ae(specif) in pgauth-accmanage 10151# Output: 10152# - list ready for get-data 10153# 10154 10155proc pgauth-build-form-spec {modif spec1 spec2} { 10156 set form {} 10157 10158 foreach c $spec1 { 10159 lassign $c title spec var user 10160 set kw [lindex $spec 0] 10161 if {$modif eq "mod"} then { 10162 if {$user} then { 10163 switch -- $kw { 10164 list { lappend form [list $var 0 99999] } 10165 default { lappend form [list $var 1 1] } 10166 } 10167 } 10168 } else { 10169 switch -- $kw { 10170 list { lappend form [list $var 1 1] } 10171 default { lappend form [list $var 1 1] } 10172 } 10173 } 10174 } 10175 10176 set nvar 0 10177 foreach c $spec2 { 10178 incr nvar 10179 set kw [lindex [lindex $c 1] 0] 10180 set var "uvar$nvar" 10181 switch -- $kw { 10182 list { lappend form [list $var 0 99999] } 10183 default { lappend form [list $var 1 1] } 10184 } 10185 } 10186 10187 return $form 10188} 10189 10190# 10191# Build a menu or a listbox with realms 10192# 10193# Input: 10194# - dbfd : database handle 10195# - type : list or menu 10196# - all : true if entry "All" should be displayed 10197# - rlmlist : list of realms to manage 10198# - maxrlm : max number of realms to display 10199# - _gidx : in return, array of indexes 10200# Output : 10201# - field ready to be displayed by form-field 10202# 10203 10204proc pgauth-build-realm-index {dbfd type all rlmlist maxrlm _gidx} { 10205 upvar $_gidx gidx 10206 10207 pgauth-lsrealm $dbfd tabrlm 10208 10209 set menurealms {} 10210 set i 0 10211 switch [llength $rlmlist] { 10212 0 { 10213 # 10214 # Menu with all available realms 10215 # 10216 if {$all} then { 10217 lappend menurealms [list "_" [mc "All"]] 10218 incr i 10219 } 10220 foreach r [lsort [array names tabrlm]] { 10221 set gidx($r) $i 10222 lappend menurealms [list $r $r] 10223 incr i 10224 } 10225 } 10226 1 { 10227 # 10228 # Don't authorize realm input 10229 # 10230 } 10231 default { 10232 # 10233 # Authorize selected realm input 10234 # 10235 if {$all} then { 10236 lappend menurealms [list "_" [mc "All"]] 10237 incr i 10238 } 10239 foreach r $rlmlist { 10240 if {[info exists tabrlm($r)]} then { 10241 set gidx($r) $i 10242 lappend menurealms [list $r $r] 10243 } else { 10244 lappend menurealms [list [mc "Invalid realm '%s'"] $r] 10245 } 10246 incr i 10247 } 10248 } 10249 } 10250 10251 set nrealms [llength $menurealms] 10252 if {$nrealms > 0} then { 10253 if {$maxrlm > 0 && $nrealms > $maxrlm} then { 10254 set nrealms $maxrlm 10255 } 10256 if {$type eq "list"} then { 10257 set menurealms [linsert $menurealms 0 "list" "multi" $nrealms] 10258 } else { 10259 set menurealms [linsert $menurealms 0 "menu"] 10260 } 10261 } 10262 10263 return $menurealms 10264} 10265 10266############################################################################## 10267# Topo library 10268############################################################################## 10269 10270# 10271# Read topo status 10272# 10273# Input: 10274# - parameters: 10275# - dbfd : database handle 10276# - admin : 1 if user is administrator 10277# Output: 10278# - return value: HTML status message, or empty (if user is not admin 10279# or if there is no message) 10280# 10281# History 10282# 2010/11/15 : pda : extract in an autonomous function 10283# 2010/11/23 : pda : use keepstate table 10284# 2010/12/04 : pda : i18n 10285# 10286 10287proc topo-status {dbfd admin} { 10288 set msgsta "" 10289 if {$admin} then { 10290 set found 0 10291 set sql "SELECT * FROM topo.keepstate WHERE type = 'anaconf'" 10292 pg_select $dbfd $sql tab { 10293 set date $tab(date) 10294 set msg $tab(message) 10295 set found 1 10296 } 10297 if {! $found} then { 10298 set msg [mc "No message from anaconf"] 10299 set date [mc "(no date)"] 10300 } elseif {$msg eq "Resuming normal operation"} then { 10301 set msg "" 10302 } 10303 10304 if {$msg ne ""} then { 10305 set msg [::webapp::html-string $msg] 10306 regsub -all "\n" $msg "<br>" msg 10307 10308 set text [::webapp::helem "p" [mc "Topod messages"]] 10309 append text [::webapp::helem "p" \ 10310 [::webapp::helem "font" $msg "color" "#ff0000"] \ 10311 ] 10312 append text [::webapp::helem "p" [mc "... since %s" $date]] 10313 10314 set msgsta [::webapp::helem "div" $text "class" "alerte"] 10315 } 10316 } 10317 return $msgsta 10318} 10319 10320# 10321# Wrapper function to call topo programs on topo host 10322# 10323# Input: 10324# - cmd: topo program with arguments 10325# - _msg : in return, text read from program or error message 10326# Output: 10327# - return value: 1 if ok, 0 if failure 10328# - parameter _msg: text read or error message 10329# 10330# History 10331# 2010/12/14 : pda/jean : design 10332# 2010/12/19 : pda : added topouser 10333# 2012/04/24 : pda : the graph file is local to the www server 10334# 10335 10336proc call-topo {cmd _msg} { 10337 upvar $_msg msg 10338 10339 # 10340 # Quote shell metacharacters to prevent interpretation 10341 # 10342 regsub -all {[<>|;'"${}()&\[\]*?]} $cmd {\\&} cmd 10343 10344 set topobindir [get-local-conf "topobindir"] 10345 set topograph [get-local-conf "topograph"] 10346 set topohost [get-local-conf "topohost"] 10347 10348 set cmd "$topobindir/$cmd < $topograph" 10349 set r [catch {exec sh -c $cmd} msg option] 10350 return [expr !$r] 10351} 10352 10353# 10354# Compare two interface names (for sort function) 10355# 10356# Input: 10357# - parameters: 10358# - i1, i2 : interface names 10359# Output: 10360# - return value: -1, 0 or 1 (see string compare) 10361# 10362# History 10363# 2006/12/29 : pda : design 10364# 2010/12/04 : pda : i18n 10365# 10366 10367proc compare-interfaces {i1 i2} { 10368 # 10369 # Isolate all words 10370 # Eg: "GigabitEthernet1/0/1" -> " GigabitEthernet 1/0/1" 10371 # 10372 regsub -all {[A-Za-z]+} $i1 { & } i1 10373 regsub -all {[A-Za-z]+} $i2 { & } i2 10374 # 10375 # Remove all special characters 10376 # Eg: " GigabitEthernet 1/0/1" -> " GigabitEthernet 1 0 1" 10377 # 10378 regsub -all {[^A-Za-z0-9]+} $i1 { } i1 10379 regsub -all {[^A-Za-z0-9]+} $i2 { } i2 10380 # 10381 # Remove unneeded spaces 10382 # 10383 set i1 [string trim $i1] 10384 set i2 [string trim $i2] 10385 10386 # 10387 # Compare word by word 10388 # 10389 set r 0 10390 foreach m1 [split $i1] m2 [split $i2] { 10391 if {[regexp {^[0-9]+$} $m1] && [regexp {^[0-9]+$} $m2]} then { 10392 if {$m1 < $m2} then { 10393 set r -1 10394 } elseif {$m1 > $m2} then { 10395 set r 1 10396 } else { 10397 set r 0 10398 } 10399 } else { 10400 set r [string compare $m1 $m2] 10401 } 10402 if {$r != 0} then { 10403 break 10404 } 10405 } 10406 10407 return $r 10408} 10409 10410# 10411# Compare two IP addresses, used in sort operations. 10412# 10413# Input: 10414# - parameters: 10415# - ip1, ip2 : IP addresses (IPv4 or IPv6) 10416# Output: 10417# - return value: -1, 0 ou 1 (see string compare) 10418# 10419# History 10420# 2006/06/20 : pda : design 10421# 2006/06/22 : pda : documentation 10422# 2010/12/04 : pda : i18n 10423# 10424 10425proc compare-ip {ip1 ip2} { 10426 set ip1 [::ip::normalize $ip1] 10427 set v1 [::ip::version $ip1] 10428 set ip2 [::ip::normalize $ip2] 10429 set v2 [::ip::version $ip2] 10430 10431 set r 0 10432 if {$v1 == 4 && $v2 == 4} then { 10433 set l1 [split [::ip::prefix $ip1] "."] 10434 set l2 [split [::ip::prefix $ip2] "."] 10435 foreach e1 $l1 e2 $l2 { 10436 if {$e1 < $e2} then { 10437 set r -1 10438 break 10439 } elseif {$e1 > $e2} then { 10440 set r 1 10441 break 10442 } 10443 } 10444 } elseif {$v1 == 6 && $v2 == 6} then { 10445 set l1 [split [::ip::prefix $ip1] ":"] 10446 set l2 [split [::ip::prefix $ip2] ":"] 10447 foreach e1 $l1 e2 $l2 { 10448 if {"0x$e1" < "0x$e2"} then { 10449 set r -1 10450 break 10451 } elseif {"0x$e1" > "0x$e2"} then { 10452 set r 1 10453 break 10454 } 10455 } 10456 } else { 10457 set r [expr $v1 < $v2] 10458 } 10459 return $r 10460} 10461 10462# 10463# Check if an IP address (IPv4 or IPv6) is in an address range 10464# 10465# Input: 10466# - parameters: 10467# - ip : IP address (or CIDR) to check 10468# - net : address range 10469# Output: 10470# - return value: 0 (ip not in range) or 1 (ip is in range) 10471# 10472# History 10473# 2006/06/22 : pda : design 10474# 2010/12/04 : pda : i18n 10475# 10476 10477proc ip-in {ip net} { 10478 set v [::ip::version $net] 10479 if {[::ip::version $ip] != $v} then { 10480 return 0 10481 } 10482 10483 set defmask [expr "$v==4 ? 32 : 128"] 10484 10485 set ip [::ip::normalize $ip] 10486 set net [::ip::normalize $net] 10487 10488 set mask [::ip::mask $net] 10489 if {$mask eq ""} then { 10490 set mask $defmask 10491 } 10492 10493 set prefnet [::ip::prefix $net] 10494 regsub {(/[0-9]+)?$} $ip "/$mask" ip2 10495 set prefip [::ip::prefix $ip2] 10496 10497 return [string equal $prefip $prefnet] 10498} 10499 10500# 10501# Check metrology id against user permissions 10502# 10503# Input: 10504# - parameters: 10505# - dbfd : database handle 10506# - id : id du point de collecte (ou id+id+...) 10507# - _tabuid : user characteristics 10508# - _title : title of graph 10509# Output: 10510# - return value: empty string or error message 10511# - parameter _title : title of graph found 10512# 10513# History 10514# 2006/08/09 : pda/boggia : design 10515# 2006/12/29 : pda : parameter vlan 10516# 2008/07/30 : pda : adapt to new extractcoll 10517# 2008/07/30 : pda : multiple ids 10518# 2008/07/31 : pda : add "|" 10519# 2010/12/04 : pda : i18n 10520# 10521 10522proc check-metro-id {dbfd id _tabuid _title} { 10523 upvar $_tabuid tabuid 10524 upvar $_title title 10525 global libconf 10526 10527 # 10528 # If ids are more than one 10529 # 10530 10531 set lid [split $id "+|"] 10532 10533 # 10534 # Get the metrology sensor list, according to user permissions 10535 # 10536 10537 set cmd [format $libconf(extractcoll) $tabuid(flagsr)] 10538 if {! [call-topo $cmd msg]} then { 10539 return [mc "Cannot read sensor list: %s" $msg] 10540 } 10541 foreach line [split $msg "\n"] { 10542 lassign [split $line] kw i 10543 set n [lsearch -exact $lid $i] 10544 if {$n >= 0} then { 10545 set idtab($i) $line 10546 if {[info exists firstkw]} then { 10547 if {$firstkw ne $kw} then { 10548 return [mc "Divergent sensor types"] 10549 } 10550 } else { 10551 set firstkw $kw 10552 } 10553 set lid [lreplace $lid $n $n] 10554 } 10555 } 10556 10557 # 10558 # Error if id is not found 10559 # 10560 10561 if {[llength $lid] > 0} then { 10562 return [mc "Sensor '%s' not found" $id] 10563 } 10564 10565 # 10566 # Try to guess an appropriate title 10567 # 10568 10569 set lid [array names idtab] 10570 switch [llength $lid] { 10571 0 { 10572 return [mc "No sensor selected"] 10573 } 10574 1 { 10575 set i [lindex $lid 0] 10576 set l $idtab($i) 10577 switch $firstkw { 10578 trafic { 10579 set eq [lindex $l 2] 10580 set iface [lindex $l 4] 10581 set vlan [lindex $l 5] 10582 10583 if {$vlan ne "-"} then { 10584 set t [mc {Traffic on vlan %1$s of interface %2$s of %3$s}] 10585 } else { 10586 set t [mc {Traffic on interface %2$s of %3$s}] 10587 } 10588 set title [format $t $vlan $iface $eq] 10589 } 10590 nbauthwifi - 10591 nbassocwifi { 10592 set eq [lindex $l 2] 10593 set iface [lindex $l 4] 10594 set ssid [lindex $l 5] 10595 10596 if {$firstkw eq "nbauthwifi"} then { 10597 set t [mc {Number of auhentified users on ssid %1$s of interface %2$s of %3$s}] 10598 } else { 10599 set t [mc {Number of associated hosts on ssid %1$s of interface %2$s of %3$s}] 10600 } 10601 set title [format $t $ssid $iface $eq] 10602 } 10603 default { 10604 return [mc "Internal error: invalid extractcoll output format"] 10605 } 10606 } 10607 } 10608 default { 10609 switch $firstkw { 10610 trafic { 10611 set le {} 10612 foreach i $lid { 10613 set l $idtab($i) 10614 set eq [lindex $l 2] 10615 set iface [lindex $l 4] 10616 set vlan [lindex $l 5] 10617 10618 set e "$eq/$iface" 10619 if {$vlan ne "-"} then { 10620 append e ".$vlan" 10621 } 10622 lappend le $e 10623 } 10624 set le [join $le ", "] 10625 set title [mc "Traffic on interfaces %s" $le] 10626 } 10627 nbauthwifi - 10628 nbassocwifi { 10629 if {$firstkw eq "nbauthwifi"} then { 10630 set t [mc "Number of auhentified users on %s"] 10631 } else { 10632 set t [mc "Number of associated hosts on %s"] 10633 } 10634 foreach i $lid { 10635 set l $idtab($i) 10636 set eq [lindex $l 2] 10637 set iface [lindex $l 4] 10638 set ssid [lindex $l 5] 10639 10640 set e "$eq/$iface ($ssid)" 10641 lappend le $e 10642 } 10643 set le [join $le ", "] 10644 set title [format $t $le] 10645 } 10646 default { 10647 return [mc "Internal error: invalid extractcoll output format"] 10648 } 10649 } 10650 } 10651 } 10652 10653 return "" 10654} 10655 10656# 10657# Get regexp giving authorized equipments for a given group. 10658# 10659# Input: 10660# - parameters: 10661# - dbfd : database handle 10662# - rw : read (0) or write (1) 10663# - idgrp : group id 10664# Output: 10665# - return value: {{re_allow_1 ... re_allow_n} {re_deny_1 ... re_deny_n}} 10666# 10667# History 10668# 2006/08/10 : pda/boggia : design with an on-disk file 10669# 2010/11/03 : pda/jean : data are now in the database 10670# 2010/12/05 : pda : i18n 10671# 10672 10673proc read-authorized-eq {dbfd rw idgrp} { 10674 set r {} 10675 foreach allow_deny {1 0} { 10676 set sql "SELECT pattern 10677 FROM topo.p_eq 10678 WHERE idgrp = $idgrp 10679 AND rw = $rw 10680 AND allow_deny = $allow_deny" 10681 set d {} 10682 pg_select $dbfd $sql tab { 10683 lappend d $tab(pattern) 10684 } 10685 lappend r $d 10686 } 10687 return $r 10688} 10689 10690# 10691# Fetch a graph from the metrology host and return it back. 10692# 10693# Input: 10694# - parameters: 10695# - url : URL of the graph on the metrology host 10696# Output: 10697# - none : the fetched graph is printed on stdout with usual HTTP headers 10698# 10699# History 10700# 2006/05/17 : jean : design for dhcplog 10701# 2006/08/09 : pda/boggia : extract and use in this library 10702# 2010/11/15 : pda : remove err parameter 10703# 2010/12/05 : pda : i18n 10704# 10705 10706proc gengraph {url} { 10707 package require http ;# tcllib 10708 10709 set token [::http::geturl $url] 10710 set status [::http::status $token] 10711 10712 if {$status ne "ok"} then { 10713 set code [::http::code $token] 10714 d error [mc "No access: %s" $code] 10715 } 10716 10717 upvar #0 $token state 10718 10719 # 10720 # Determine image type 10721 # 10722 10723 array set meta $state(meta) 10724 switch -exact $meta(Content-Type) { 10725 image/png { 10726 set contenttype "png" 10727 } 10728 image/jpeg { 10729 set contenttype "jpeg" 10730 } 10731 image/gif { 10732 set contenttype "gif" 10733 } 10734 default { 10735 set contenttype "html" 10736 } 10737 } 10738 10739 # 10740 # Return the result back 10741 # 10742 10743 ::webapp::send $contenttype $state(body) 10744} 10745 10746# 10747# Decode a date (supposed to be input by a human) 10748# 10749# Input: 10750# - parameters: 10751# - date : date imput by an user in a form 10752# - hour : hour (from 00:00:00 to 23:59:59) 10753# Output: 10754# - return value: converted date in potsgresql format, or "" if no date 10755# 10756# History 10757# 2000/07/18 : pda : design 10758# 2000/07/23 : pda : add hour 10759# 2001/03/12 : pda : extract in this library 10760# 2008/07/30 : pda : add special case for 24h (= 23:59:59) 10761# 2010/12/05 : pda : i18n 10762# 10763 10764proc decode-date {date hour} { 10765 set date [string trim $date] 10766 if {$date eq ""} then { 10767 set datepg "" 10768 } 10769 if {$hour eq "24"} then { 10770 set hour "23:59:59" 10771 } 10772 set l [split $date "/"] 10773 lassign $l dd mm yyyy 10774 switch [llength $l] { 10775 1 { 10776 set mm [clock format [clock seconds] -format "%m"] 10777 set yyyy [clock format [clock seconds] -format "%Y"] 10778 set datepg "$mm/$dd/$yyyy $hour" 10779 } 10780 2 { 10781 set yyyy [clock format [clock seconds] -format "%Y"] 10782 set datepg "$mm/$dd/$yyyy $hour" 10783 } 10784 3 { 10785 set datepg "$mm/$dd/$yyyy $hour" 10786 } 10787 default { 10788 set datepg "" 10789 } 10790 } 10791 10792 if {$datepg ne ""} then { 10793 if {[catch {clock scan $datepg}]} then { 10794 set datepg "" 10795 } 10796 } 10797 return $datepg 10798} 10799 10800# 10801# Convert a 802.11b/g radio frequency (2.4 GHz band) into a channel 10802# 10803# Input: 10804# - parameters: 10805# - freq : frequency 10806# - global variable libconf(freq:<frequency>) : conversion table 10807# Output: 10808# - return value: channel 10809# 10810# History 10811# 2008/07/30 : pda : design 10812# 2008/10/17 : pda : channel "dfs" 10813# 2010/12/05 : pda : i18n 10814# 10815 10816proc conv-channel {freq} { 10817 global libconf 10818 10819 switch -- $freq { 10820 dfs { 10821 set channel "auto" 10822 } 10823 default { 10824 if {[info exists libconf(freq:$freq)]} then { 10825 set channel $libconf(freq:$freq) 10826 } else { 10827 set channel "$freq MHz" 10828 } 10829 } 10830 } 10831 return $channel 10832} 10833 10834# 10835# Read list of interfaces on an equipment 10836# 10837# Input: 10838# - parameters: 10839# - eq : equipment name 10840# - _tabuid : user's characteristics (including graph flags) 10841# - global variables : 10842# - libconf(extracteq) : call to extracteq 10843# Output: 10844# - return value: {eq type model location iflist liferr arrayif arrayvlan} 10845# where 10846# - iflist is the sorted list of interfaces 10847# - liferr is the list of interfaces which are are writable but not 10848# readable (e.g. this is an error) 10849# - arrayif (ready for "array set") gives an array indexed by 10850# interface name: 10851# tab(iface) {name edit radio stat mode desc link native {vlan...}} 10852# (see extracteq output format) 10853# - arrayvlan (ready for "array set") gives an array indexed by vlanid: 10854# tab(id) {desc-in-hex voip-0-or-1} 10855# 10856# History 10857# 2010/11/03 : pda : design 10858# 2010/11/15 : pda : remove parameter err 10859# 2010/11/23 : pda/jean : get writable interfaces 10860# 2010/11/25 : pda : add manual 10861# 2010/12/05 : pda : i18n 10862# 10863 10864proc eq-iflist {eq _tabuid} { 10865 global libconf 10866 upvar $_tabuid tabuid 10867 10868 # 10869 # First call to extracteq : get the list of "readable" interfaces 10870 # 10871 10872 set found 0 10873 10874 set cmd [format $libconf(extracteq) $tabuid(flagsr) $eq] 10875 if {! [call-topo $cmd msg]} then { 10876 d error [mc {Error during extraction of readable interfaces from '%1$s': %2$s} $eq $msg] 10877 } 10878 foreach line [split $msg "\n"] { 10879 switch [lindex $line 0] { 10880 eq { 10881 set r [lreplace $line 0 0] 10882 10883 set location [lindex $r 3] 10884 if {$location eq "-"} then { 10885 set location "" 10886 } else { 10887 set location [binary format H* $location] 10888 } 10889 set r [lreplace $r 3 3 $location] 10890 10891 # manual = "manual" or "auto" 10892 set manual [lindex $r 4] 10893 set r [lreplace $r 4 4] 10894 10895 set found 1 10896 } 10897 iface { 10898 set if [lindex $line 1] 10899 # prepare "edit" item, which may be set in the second 10900 # call to extracteq 10901 set line [linsert $line 2 "-"] 10902 set tabiface($if) [lreplace $line 0 0] 10903 } 10904 } 10905 } 10906 10907 if {! $found} then { 10908 d error [mc "Equipment '%s' not found" $eq] 10909 } 10910 10911 # 10912 # Second call to exctracteq : get the list of "writable" interfaces 10913 # 10914 10915 set liferr {} 10916 10917 if {$manual eq "auto"} then { 10918 set cmd [format $libconf(extracteq) $tabuid(flagsw) $eq] 10919 if {! [call-topo $cmd msg]} then { 10920 d error [mc {Error during extraction of writable interfaces from '%1$s': %2$s} $eq $msg] 10921 } 10922 foreach line [split $msg "\n"] { 10923 switch [lindex $line 0] { 10924 iface { 10925 set if [lindex $line 1] 10926 if {! [info exists tabiface($if)]} then { 10927 # add this interface to the list of error interfaces 10928 lappend liferr $if 10929 } else { 10930 # set the "edit" attribute on this interface 10931 set tabiface($if) [lreplace $tabiface($if) 1 1 "edit"] 10932 } 10933 } 10934 vlan { 10935 lassign $line bidon id desc voip 10936 set tabvlan($id) [list $desc $voip] 10937 } 10938 } 10939 } 10940 set liferr [lsort -command compare-interfaces $liferr] 10941 } 10942 10943 lappend r $liferr 10944 10945 # 10946 # Sort interfaces 10947 # 10948 10949 set iflist [lsort -command compare-interfaces [array names tabiface]] 10950 10951 # 10952 # Return value 10953 # 10954 10955 lappend r $iflist 10956 lappend r [array get tabiface] 10957 lappend r [array get tabvlan] 10958 10959 return $r 10960} 10961 10962# 10963# Get graph and equipment status 10964# 10965# Input: 10966# - parameters: 10967# - dbfd : database handle 10968# - eq : equipment name 10969# - iface (optional) : interface name 10970# Output: 10971# - return value: HTML text giving graph and equipment status 10972# 10973# History: 10974# 2010/11/29 : pda/jean : design 10975# 2010/12/05 : pda : i18n 10976# 10977 10978proc eq-graph-status {dbfd eq {iface {}}} { 10979 global libconf 10980 10981 # 10982 # Search for unprocessed modifications and build information. 10983 # 10984 10985 set wif "" 10986 if {$iface ne ""} then { 10987 set qiface [::pgsql::quote $iface] 10988 set wif "AND iface = '$qiface'" 10989 } 10990 10991 set qeq [::pgsql::quote $eq] 10992 10993 set sql "SELECT * FROM topo.ifchanges 10994 WHERE eq = '$qeq' AND processed = 0 $wif 10995 ORDER BY reqdate DESC" 10996 set lines {} 10997 lappend lines [list Title4 [mc "Date"] [mc "Login"] [mc "Interface"] [mc "Change"]] 10998 pg_select $dbfd $sql tab { 10999 set ifdesc $tab(ifdesc) 11000 set ethervlan $tab(ethervlan) 11001 set voicevlan $tab(voicevlan) 11002 set chg [mc "description='%s'" $ifdesc] 11003 if {$ethervlan == -1} then { 11004 append chg ", " 11005 append chg [mc "deactivated interface"] 11006 } else { 11007 append chg ", " 11008 append chg [mc "vlan=%s" $ethervlan] 11009 if {$voicevlan != -1} then { 11010 append chg ", " 11011 append chg [mc "voip=%s" $voicevlan] 11012 } 11013 } 11014 lappend lines [list Normal4 $tab(reqdate) $tab(login) $tab(iface) $chg] 11015 } 11016 if {[llength $lines] == 1} then { 11017 set ifchg "" 11018 } else { 11019 set ifchg [::webapp::helem "p" [mc "Changes currently processed:"]] 11020 append ifchg [::arrgen::output "html" $libconf(tabeqstatus) $lines] 11021 } 11022 11023 # 11024 # Search for current topod status 11025 # 11026 11027 set sql "SELECT message FROM topo.keepstate WHERE type = 'status'" 11028 set action "" 11029 pg_select $dbfd $sql tab { 11030 catch {lassign [lindex $tab(message) 0] date action} 11031 } 11032 11033 switch -nocase -glob $action { 11034 rancid* - 11035 building* { 11036 set graph [::webapp::helem "p" [mc "Graph currenty re-builded. Informations presented here are not necessarily consistent with current equipement configuration."]] 11037 } 11038 default { 11039 set graph "" 11040 } 11041 } 11042 11043 # 11044 # Present information from $ifchg and $graph 11045 # 11046 11047 if {$ifchg eq "" && $graph eq ""} then { 11048 set html "" 11049 } else { 11050 set html "$graph\n$ifchg" 11051 set html [::webapp::helem "font" $html "color" "#ff0000"] 11052 set html "<hr>$html<hr>" 11053 } 11054 11055 return $html 11056} 11057 11058 11059# 11060# Check if a VLAN name is valid 11061# 11062# Input: 11063# - parameters: 11064# - name : VLAN name 11065# - _msg : error message 11066# - global variable libconf(vlan-chars) : authorized characters 11067# Output: 11068# - return value: 1 if name is valid, 0 otherwise 11069# - msg: error message 11070# 11071# History 11072# 2014/02/18 : jean : converted to function from "list-vlans" 11073# 11074 11075proc check-vlan-name {name _msg} { 11076 global libconf 11077 upvar $_msg msg 11078 11079 11080 if {[regexp "^\[$libconf(vlan-chars)\]+$" $name]} then { 11081 set ok 1 11082 set msg "" 11083 } else { 11084 set ok 0 11085 set msg "invalid characters in vlan name '$name' (not in $libconf(vlan-chars))" 11086 } 11087 11088 return $ok 11089} 11090 11091 11092############################################################################## 11093# Topo*d subsystem 11094############################################################################## 11095 11096# 11097# Set function tracing 11098# 11099# Input: 11100# - lfunct : list of function names 11101# Output: none 11102# 11103# History 11104# 2010/10/20 : pda/jean : minimal design 11105# 2010/12/15 : pda/jean : splitted in library 11106# 11107 11108proc set-trace {lfunct} { 11109 foreach c $lfunct { 11110 trace add execution $c enter report-enter 11111 trace add execution $c leave report-leave 11112 } 11113} 11114 11115proc report-enter {cmd enter} { 11116 puts "> $cmd" 11117} 11118 11119proc report-leave {cmd code result leave} { 11120 puts "< $cmd -> $code/$result" 11121} 11122 11123# 11124# Run a program as a daemon 11125# 11126# Input: 11127# - argv0 : path to the script 11128# - argstr : argument string 11129# Output: none 11130# 11131# History 11132# 2012/03/27 : pda/jean : design 11133# 11134 11135proc run-as-daemon {argv0 argstr} { 11136 exec sh -c "exec $argv0 $argstr" & 11137 exit 0 11138} 11139 11140############################################################################## 11141# Utility functions 11142############################################################################## 11143 11144# 11145# Initialize system logger 11146# 11147# Input: 11148# - logger : shell command line to log messages 11149# Output: none 11150# 11151# History 11152# 2010/12/15 : pda/jean : minimal design 11153# 11154 11155set ctxt(logger) "" 11156 11157proc set-log {logger} { 11158 global ctxt 11159 11160 set ctxt(logger) $logger 11161} 11162 11163# 11164# Add a message to the log 11165# 11166# Input: 11167# - msg : error/warning message 11168# Output: none 11169# 11170# History 11171# 2010/10/20 : pda/jean : minimal design 11172# 11173 11174proc log-error {msg} { 11175 global ctxt 11176 11177 if {[catch {open "|$ctxt(logger)" "w"} fd]} then { 11178 puts stderr "$msg (log to syslog: $fd)" 11179 } else { 11180 puts $fd $msg 11181 close $fd 11182 } 11183} 11184 11185# 11186# Set verbosity level 11187# 11188# Input: 11189# - level : threshold (verbosity level) of messages to display 11190# Output: 11191# - return value: none 11192# - ctxt(verbose) : verbose threshold 11193# 11194# History 11195# 2010/10/21 : pda/jean : design 11196# 11197 11198proc topo-set-verbose {level} { 11199 global ctxt 11200 11201 set ctxt(verbose) $level 11202} 11203 11204# 11205# Display debug message according to verbosity level 11206# 11207# Input: 11208# - msg : message 11209# - level : verbosity level 11210# Output: none 11211# 11212# History 11213# 2010/10/21 : pda/jean : design 11214# 11215 11216proc topo-verbositer {msg level} { 11217 global ctxt 11218 11219 if {$level <= $ctxt(verbose)} then { 11220 puts stderr $msg 11221 } 11222} 11223 11224############################################################################## 11225# Status management 11226############################################################################## 11227 11228# 11229# Update status 11230# Status keeps last topo*d operations. 11231# 11232# Input: 11233# - status : current operation 11234# Output: none 11235# 11236# Note: status is in topo.keepstate table, topo.message is a list 11237# {{date1 msg1} {date2 msg2} ...} where 1 is the most recent entry. 11238# We keep only last N entries. 11239# 11240# History 11241# 2010/11/05 : pda/jean : design 11242# 11243 11244proc reset-status {} { 11245 set sql "DELETE FROM topo.keepstate WHERE type = 'status'" 11246 toposqlexec $sql 2 11247} 11248 11249proc set-status {status} { 11250 global ctxt 11251 11252 set cur {} 11253 set sql "SELECT message FROM topo.keepstate WHERE type = 'status'" 11254 if {! [toposqlselect $sql tab { set cur $tab(message) } 2]} then { 11255 return 11256 } 11257 11258 # insert new entry before all others 11259 set date [clock format [clock seconds]] 11260 set cur [linsert $cur 0 [list $date $status]] 11261 11262 # remove oldest entries at the end 11263 if {[llength $cur] > $ctxt(maxstatus)} then { 11264 set cur [lreplace $cur $ctxt(maxstatus) end] 11265 } 11266 11267 set qcur [::pgsql::quote $cur] 11268 11269 set sql "DELETE FROM topo.keepstate WHERE type = 'status' ; 11270 INSERT INTO topo.keepstate (type, message) 11271 VALUES ('status', '$qcur')" 11272 toposqlexec $sql 2 11273} 11274 11275############################################################################## 11276# Topo*d database handling 11277############################################################################## 11278 11279# 11280# Connect to database if needed 11281# 11282# Input: 11283# - chan : database channel 11284# - ctxt(dbfd1), ctxt(dbfd2) : database handles for each channel 11285# Output: 11286# - ctxt(dbfd<n>) : database handle updated 11287# 11288# History 11289# 2010/10/20 : pda/jean : documentation 11290# 11291 11292proc lazy-connect {{chan 1}} { 11293 global ctxt 11294 11295 set r 1 11296 if {[string equal $ctxt(dbfd$chan) ""]} then { 11297 set conninfo [get-conninfo "dnsdb"] 11298 set d [catch {set ctxt(dbfd$chan) [pg_connect -conninfo $conninfo]} msg] 11299 if {$d} then { 11300 set r 0 11301 } else { 11302 ::dnsconfig setdb $ctxt(dbfd$chan) 11303 log-error "Connexion to database succeeded" 11304 } 11305 } 11306 return $r 11307} 11308 11309# 11310# Execute a SQL request to get data (as with pg_select), and manage 11311# database reconnect 11312# 11313# Input: 11314# - sql : SQL request 11315# - arrayname : array used in the script 11316# - script : procedure ou script 11317# - chan : optionnal channel (1 or 2) 11318# Output: 11319# - return value: 1 if ok, 0 if error 11320# 11321# History 11322# 2010/10/20 : pda/jean : design (woaw !) 11323# 11324 11325proc toposqlselect {sql arrayname script {chan 1}} { 11326 global ctxt 11327 11328 if {[lazy-connect $chan]} { 11329 set cmd [list pg_select $ctxt(dbfd$chan) $sql $arrayname $script] 11330 if {[catch {uplevel 1 $cmd} err]} then { 11331 log-error "Connexion to database lost in toposqlselect ($err)" 11332 catch {pg_disconnect $ctxt(dbfd$chan)} 11333 set ctxt(dbfd$chan) "" 11334 set r 0 11335 } else { 11336 set r 1 11337 } 11338 } else { 11339 set r 0 11340 } 11341 return $r 11342} 11343 11344# 11345# Execute a SQL request to modify data (INSERT, UPDATE or DELETE, as 11346# with pg_exec) and manage database reconnect 11347# 11348# Input: 11349# - sql : SQL request 11350# - chan : optionnal channel (1 or 2) 11351# Output: 11352# - return value: 1 if ok, 0 if error 11353# 11354# History 11355# 2010/10/20 : pda/jean : design 11356# 11357 11358proc toposqlexec {sql {chan 1}} { 11359 global ctxt 11360 11361 if {[lazy-connect]} { 11362 if {[catch {pg_exec $ctxt(dbfd$chan) $sql} res]} then { 11363 log-error "Connection to database lost in toposqlexec ($res)" 11364 catch {pg_disconnect $ctxt(dbfd$chan)} 11365 set ctxt(dbfd$chan) "" 11366 set r 0 11367 } else { 11368 switch -- [pg_result $res -status] { 11369 PGRES_COMMAND_OK - 11370 PGRES_TUPLES_OK - 11371 PGRES_EMPTY_QUERY { 11372 set r 1 11373 pg_result $res -clear 11374 } 11375 default { 11376 set err [pg_result $res -error] 11377 pg_result $res -clear 11378 log-error "Internal error in toposqlexec. Connexion to database lost ($err)" 11379 catch {pg_disconnect $ctxt(dbfd$chan)} 11380 set ctxt(dbfd$chan) "" 11381 set r 0 11382 } 11383 } 11384 } 11385 } else { 11386 set r 0 11387 } 11388 return $r 11389} 11390 11391# 11392# Start a SQL transaction and manage database reconnect 11393# 11394# Input: 11395# - chan : optionnal channel (1 or 2) 11396# Output: 11397# - return value: 1 if ok, 0 if error 11398# 11399# History 11400# 2010/10/21 : pda/jean : design 11401# 11402 11403proc toposqllock {{chan 1}} { 11404 return [toposqlexec "START TRANSACTION" $chan] 11405} 11406 11407# 11408# End a SQL transaction and manage database reconnect 11409# 11410# Input: 11411# - commit : "commit" or "abort" 11412# Output: 11413# - return value: 1 if ok, 0 if error 11414# 11415# History 11416# 2010/10/21 : pda/jean : design 11417# 11418 11419proc toposqlunlock {commit {chan 1}} { 11420 switch $commit { 11421 commit { set sql "COMMIT WORK" } 11422 abort { set sql "ABORT WORK" } 11423 } 11424 return [toposqlexec $sql $chan] 11425} 11426 11427 11428############################################################################## 11429# Topo*d mail management 11430############################################################################## 11431 11432# 11433# Send a mail if event message changes 11434# 11435# Input: 11436# - ev : event ("rancid", "anaconf", etc.) 11437# - msg : event message 11438# Output: 11439# - none 11440# 11441# History 11442# 2010/10/21 : pda/jean : design 11443# 11444 11445proc keep-state-mail {ev msg} { 11446 # 11447 # Get previous message 11448 # 11449 11450 set oldmsg "" 11451 set qev [::pgsql::quote $ev] 11452 set sql "SELECT message FROM topo.keepstate WHERE type = '$qev'" 11453 if {! [toposqlselect $sql tab { set oldmsg $tab(message) } 2]} then { 11454 # we don't know what to do... 11455 return 11456 } 11457 11458 if {$msg ne $oldmsg} then { 11459 # 11460 # New message is different from previous one. We must 11461 # send it by mail and store it in keepstate table. 11462 # 11463 # Design choice: if database access is out of order, we 11464 # can't access keepstate. The choice is to not send mail. 11465 # The risk is we won't known new messages, but the advantage 11466 # is that our mailboxes will not be polluted by a new 11467 # identical mail every X seconds. On the other hand, risk 11468 # is minimized by the fact that no new change will be detected 11469 # and/or processed while database is out of order. 11470 # 11471 11472 set qmsg [::pgsql::quote $msg] 11473 set sql "DELETE FROM topo.keepstate WHERE type = '$qev' ; 11474 INSERT INTO topo.keepstate (type, message) 11475 VALUES ('$qev', '$qmsg')" 11476 if {[toposqlexec $sql 2]} then { 11477 # 11478 # Database access is ok. Send the mail. 11479 # 11480 11481 set from [::dnsconfig get "topofrom"] 11482 set to [::dnsconfig get "topoto"] 11483 set replyto "" 11484 set cc "" 11485 set bcc "" 11486 set subject "\[auto\] topod status changed for $ev" 11487 ::webapp::mail $from $replyto $to $cc $bcc $subject $msg 11488 } 11489 } 11490} 11491 11492############################################################################## 11493# Equipment types 11494############################################################################## 11495 11496# 11497# Read type and model for all equipments in the graph. 11498# 11499# Input: 11500# - _tabeq : name of array containing, in return, types and models 11501# Output: 11502# - return value: empty string or error message 11503# - tabeq : array, indexed by FQDN of equipement, containing: 11504# tabeq(<eq>) {<type> <model>} 11505# 11506# History 11507# 2010/02/25 : pda/jean : design 11508# 2010/10/21 : pda/jean : manage only fully qualified host names 11509# 11510 11511set libconf(dumpgraph-read-eq-type) "dumpgraph -a -o eq" 11512 11513proc read-eq-type {_tabeq} { 11514 global libconf 11515 upvar $_tabeq tabeq 11516 11517 set-status "Reading equipement types" 11518 11519 set defdom [dnsconfig get "defdomain"] 11520 11521 set cmd $libconf(dumpgraph-read-eq-type) 11522 11523 if {[call-topo $cmd msg]} then { 11524 foreach line [split $msg "\n"] { 11525 switch [lindex $line 0] { 11526 eq { 11527 array set t $line 11528 set eq $t(eq) 11529 set type $t(type) 11530 set model $t(model) 11531 11532 append eq ".$defdom" 11533 11534 set tabeq($eq) [list $type $model] 11535 11536 array unset t 11537 } 11538 } 11539 } 11540 set msg "" 11541 } 11542 11543 return $msg 11544} 11545 11546############################################################################## 11547# Detection of modifications in files 11548############################################################################## 11549 11550# 11551# Detect modifications in a directory 11552# 11553# Input: 11554# - dir : directory path 11555# - _err : in return, empty string or error message 11556# Output: 11557# - return value : list {{<code> <file> <date>} {<code> <file> <date>}...} 11558# where <code> = "add", "del", "mod" or "err" 11559# and <date> = date in clock_t format 11560# if <code> = "err", error message is in "<date>" 11561# - parameter err : in return, all error messages 11562# 11563# History 11564# 2010/11/12 : pda/jean : design 11565# 11566 11567proc detect-dirmod {dir _err} { 11568 upvar $_err err 11569 11570 set err "" 11571 11572 # 11573 # First pass: get all files in directory and keep them in an array: 11574 # ntab(<file>) <date> 11575 # 11576 foreach file [glob -nocomplain "$dir/*.eq"] { 11577 if {[catch {file mtime $file} date]} then { 11578 append err "$date\n" 11579 } else { 11580 set ntab($file) $date 11581 } 11582 } 11583 11584 # 11585 # Second pass: get all files in database for this directory and 11586 # keep them in an array: 11587 # otab(<file>) <date> 11588 # 11589 set sql "SELECT path, date FROM topo.filemonitor 11590 WHERE path ~ '^$dir/\[^/\]+$'" 11591 if {! [toposqlselect $sql tab { set otab($tab(path)) [clock scan $tab(date)] }]} then { 11592 append err "Cannot execute SQL SELECT query for $dir\n" 11593 return {} 11594 } 11595 11596 # 11597 # Difference analysis 11598 # 11599 set r {} 11600 if {$err eq ""} then { 11601 foreach f [array names otab] { 11602 if {[info exists ntab($f)]} then { 11603 if {$otab($f) != $ntab($f)} then { 11604 lappend r [list "mod" $f $ntab($f)] 11605 } 11606 unset ntab($f) 11607 } else { 11608 lappend r [list "del" $f ""] 11609 } 11610 unset otab($f) 11611 } 11612 11613 foreach f [array names ntab] { 11614 lappend r [list "add" $f $ntab($f)] 11615 } 11616 } 11617 11618 return $r 11619} 11620 11621# 11622# Detect if a file has been modified 11623# 11624# Input: 11625# - path : directory path 11626# Output: 11627# - return value : see detect-dirmod 11628# 11629# History 11630# 2010/11/12 : pda/jean : design 11631# 11632 11633proc detect-filemod {path} { 11634 set oldfmod -1 11635 set qpath [::pgsql::quote $path] 11636 set sql "SELECT date FROM topo.filemonitor WHERE path = '$qpath'" 11637 if {[toposqlselect $sql tab {set oldfmod [clock scan $tab(date)]}]} then { 11638 if {[catch {file mtime $path} newfmod]} then { 11639 # 11640 # Error: we suppose that file does not exist 11641 # 11642 if {$oldfmod == -1} then { 11643 # file did not exist before, does not exists now 11644 set r [list "err" $path "Error on '$path': $newfmod"] 11645 } else { 11646 # file was existing, but not now 11647 set r [list "del" $path ""] 11648 } 11649 set newfmod "" 11650 } else { 11651 # 11652 # File exists 11653 # 11654 if {$oldfmod == -1} then { 11655 # the file is new 11656 set r [list "add" $path $newfmod] 11657 } elseif {$oldfmod == $newfmod} then { 11658 # dates are the same: file has not been modified 11659 set r {} 11660 } else { 11661 # file is modified 11662 set r [list "mod" $path $newfmod] 11663 } 11664 } 11665 } else { 11666 set r [list $path "err" "Error on '$path' : SQL query failed"] 11667 } 11668 topo-verbositer "detect-filemod: $path => $r" 9 11669 11670 return $r 11671} 11672 11673# 11674# Update file modification times in database 11675# 11676# Input: 11677# - lf : list (see detect-dirmod for format) 11678# Output: 11679# - return value : 1 if ok, 0 if error 11680# 11681# History 11682# 2010/11/12 : pda/jean : design 11683# 11684 11685proc sync-filemonitor {lf} { 11686 set sql {} 11687 foreach f $lf { 11688 lassign $f code path date 11689 set qpath [::pgsql::quote $path] 11690 switch $code { 11691 add { 11692 set qdate [clock format $date] 11693 lappend sql "INSERT INTO topo.filemonitor (path, date) 11694 VALUES ('$qpath', '$qdate')" 11695 } 11696 mod { 11697 set qdate [clock format $date] 11698 lappend sql "UPDATE topo.filemonitor 11699 SET date = '$qdate' 11700 WHERE path = '$qpath'" 11701 } 11702 del { 11703 lappend sql "DELETE FROM topo.filemonitor 11704 WHERE path = '$qpath'" 11705 } 11706 } 11707 } 11708 set r 1 11709 if {[llength $sql] > 0} then { 11710 set sql [join $sql ";"] 11711 set r [toposqlexec $sql] 11712 } 11713 11714 return $r 11715} 11716