00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require Tcl 8.4
00013 package require snit ;
00014 package require uri 1.1.5 ;
00015 package require base64 ;
00016 package require ldap 1.6 ;
00017
00018 package provide ldapx 1.0
00019
00020
00021
00022
00023
00024 snit::type ::ldapx::entry {
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 variable format "uninitialized"
00039
00040
00041
00042
00043
00044 variable dn ""
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057 variable attrvals -array {}
00058
00059 variable backup 0
00060 variable bckav -array {}
00061 variable bckdn ""
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079 variable change ""
00080
00081
00082
00083
00084
00085
00086
00087 ret reset () {
00088
00089 set format "uninitialized"
00090 set dn ""
00091 array unset attrvals
00092 set backup 0
00093 array unset bckav
00094 set bckdn ""
00095 set change ""
00096 }
00097
00098
00099
00100 ret format () {
00101
00102 return $format
00103 }
00104
00105
00106
00107
00108 ret compatible (type ref) {
00109
00110 if {$format eq "uninitialized"} then {
00111 set format $ref
00112 } elseif {$format ne $ref} then {
00113 return -code error \
00114 "Invalid operation on format $format (should be $ref)"
00115 }
00116 }
00117
00118
00119
00120 ret dn (optional newdn ={-)} {
00121
00122 if {$newdn ne "-"} then {
00123 dn = $newdn
00124 }
00125 return $dn
00126 }
00127
00128
00129
00130 ret superior () {
00131
00132 set pos [string first "," $dn]
00133 if {$pos == -1} then {
00134 set r ""
00135 } else {
00136 set r [string range $dn [expr {$pos+1}] end]
00137 }
00138 return $r
00139 }
00140
00141
00142
00143 ret rdn () {
00144
00145 set pos [string first "," $dn]
00146 if {$pos == -1} then {
00147 set r ""
00148 } else {
00149 set r [string range $dn 0 [expr {$pos-1}]]
00150 }
00151 return $r
00152 }
00153
00154
00155
00156 ret print () {
00157
00158 set r "dn: $dn"
00159 switch -- $format {
00160 uninitialized {
00161 # nothing
00162 }
00163 standard {
00164 foreach a [lsort [array names attrvals]] {
00165 append r "\n$a: $attrvals($a)"
00166 }
00167 }
00168 change {
00169 if {[llength $change]} then {
00170 append r "\n$change"
00171 }
00172 }
00173 default {
00174 append r " (inconsistent value)"
00175 }
00176 }
00177 return $r
00178 }
00179
00180
00181
00182 ret debug () {
00183
00184 set r "dn = <$dn>\nformat = $format"
00185 switch -- $format {
00186 uninitialized {
00187 # nothing
00188 }
00189 standard {
00190 foreach a [lsort [array names attrvals]] {
00191 append r "\n\t$a: $attrvals($a)"
00192 }
00193 if {$backup} then {
00194 append r "\nbackup dn = $bckdn"
00195 foreach a [lsort [array names bckav]] {
00196 append r "\n\t$a: $bckav($a)"
00197 }
00198 } else {
00199 append r "\nno backup"
00200 }
00201 }
00202 change {
00203 if {[llength $change]} then {
00204 append r "\n$change"
00205 } else {
00206 append r "\nno change"
00207 }
00208 }
00209 default {
00210 append r " (inconsistent value)"
00211 }
00212 }
00213 return $r
00214 }
00215
00216
00217
00218
00219
00220
00221
00222
00223 ret isempty () {
00224
00225 $self compatible "standard"
00226
00227 return [expr {[array size attrvals] == 0}]
00228 }
00229
00230
00231
00232 ret get (type attr) {
00233
00234 $self compatible "standard"
00235
00236 set a [string tolower $attr]
00237 if {[info exists attrvals($a)]} then {
00238 set r $attrvals($a)
00239 } else {
00240 set r {}
00241 }
00242 return $r
00243 }
00244
00245
00246
00247 ret get1 (type attr) {
00248
00249 return [lindex [$self get $attr] 0]
00250 }
00251
00252
00253
00254
00255 ret set (type attr , type vals) {
00256
00257 $self compatible "standard"
00258
00259 set a [string tolower $attr]
00260 if {[llength $vals]} then {
00261 set attrvals($a) $vals
00262 } else {
00263 unset -nocomplain attrvals($a)
00264 }
00265 return $vals
00266 }
00267
00268
00269
00270 ret set1 (type attr , type val) {
00271
00272 if {$val eq ""} then {
00273 set l {}
00274 } else {
00275 set l [list $val]
00276 }
00277
00278 return [$self set $attr $l]
00279 }
00280
00281
00282
00283 ret add (type attr , type vals) {
00284
00285 $self compatible "standard"
00286
00287 set a [string tolower $attr]
00288 foreach v $vals {
00289 lappend attrvals($a) $v
00290 }
00291 return $attrvals($a)
00292 }
00293
00294
00295
00296 ret add1 (type attr , type val) {
00297
00298 return [$self add $attr [list $val]]
00299 }
00300
00301
00302
00303 ret del (type attr , optional vals ={)} {
00304
00305 $self compatible "standard"
00306
00307 set a [string tolower $attr]
00308 if {[llength $vals]} then {
00309 l = [$self get $attr]
00310 foreach v $vals {
00311 while {[ pos = [lsearch -exact $l $v]] != -1} {
00312 l = [lreplace $l $pos $pos]
00313 }
00314 }
00315 } else {
00316 l = {}
00317 }
00318
00319 if {[llength $l]} then {
00320 $self $attr = $l
00321 } else {
00322 un -nocomplain = attrvals($a)
00323 }
00324 return
00325 }
00326
00327
00328
00329 ret del1 (type attr , type val) {
00330
00331 $self del $attr [list $val]
00332 }
00333
00334
00335
00336 ret getattr () {
00337
00338 $self compatible "standard"
00339
00340 return [array names attrvals]
00341 }
00342
00343
00344
00345 ret getall () {
00346
00347 $self compatible "standard"
00348
00349 return [array get attrvals]
00350 }
00351
00352
00353
00354 ret setall (type lst) {
00355
00356 $self compatible "standard"
00357
00358 array unset attrvals
00359 foreach {attr vals} $lst {
00360 set a [string tolower $attr]
00361 set attrvals($a) $vals
00362 }
00363 }
00364
00365
00366
00367 ret backup (optional other ={)} {
00368
00369 $self compatible "standard"
00370
00371 if {$other eq ""} then {
00372
00373
00374
00375 backup = 1
00376 bckdn = $dn
00377
00378 array un bckav =
00379 array bckav = [array get attrvals]
00380 } else {
00381
00382
00383
00384 $other compatible "standard"
00385 $other dn $dn
00386 $other all = [array get attrvals]
00387 }
00388 }
00389
00390
00391
00392 ret restore (optional other ={)} {
00393
00394 $self compatible "standard"
00395
00396 if {$backup} then {
00397 if {$other eq ""} then {
00398
00399
00400
00401 dn = $bckdn
00402 array un attrvals =
00403 array attrvals = [array get bckav]
00404 } else {
00405
00406
00407
00408 $other compatible "standard"
00409 $other dn $bckdn
00410 $other all = [array get bckav]
00411 }
00412 } else {
00413 return -code error \
00414 "Cannot restore a non backuped object"
00415 }
00416 }
00417
00418
00419
00420 ret swap () {
00421
00422 $self compatible "standard"
00423
00424 if {$backup} then {
00425 #
00426 # Swap current and backup contexts
00427 #
00428 set swdn $dn
00429 set dn $bckdn
00430 set bckdn $swdn
00431
00432 set swav [array get attrvals]
00433 array unset attrvals
00434 array set attrvals [array get bckav]
00435 array unset bckav
00436 array set bckav $swav
00437 } else {
00438 return -code error \
00439 "Cannot swap a non backuped object"
00440 }
00441 }
00442
00443
00444
00445 ret apply (type chg) {
00446
00447 $self compatible "standard"
00448 $chg compatible "change"
00449
00450 #
00451 # Apply $chg modifications to $self
00452 #
00453
00454 foreach mod [$chg change] {
00455 set op [lindex $mod 0]
00456 switch -- $op {
00457 add {
00458 if {! [$self isempty]} then {
00459 return -code error \
00460 "Cannot add an entry to a non-empty entry"
00461 }
00462 $self setall [lindex $mod 1]
00463 if {[string equal [$self dn] ""]} then {
00464 $self dn [$chg dn]
00465 }
00466 }
00467 mod {
00468 foreach submod [lindex $mod 1] {
00469 set subop [lindex $submod 0]
00470 set attr [lindex $submod 1]
00471 set vals [lindex $submod 2]
00472 switch -- $subop {
00473 modadd {
00474 $self add $attr $vals
00475 }
00476 moddel {
00477 $self del $attr $vals
00478 }
00479 modrepl {
00480 $self del $attr
00481 $self add $attr $vals
00482 }
00483 default {
00484 return -code error \
00485 "Invalid submod operation '$subop'"
00486 }
00487 }
00488 }
00489 }
00490 del {
00491 array unset attrvals
00492 }
00493 modrdn {
00494 set newrdn [lindex $mod 1]
00495 set delold [lindex $mod 2]
00496 set newsup [lindex $mod 3]
00497
00498 if {! [regexp {^([^=]+)=([^,]+)$} $newrdn m nattr nval]} then {
00499 return -code "Invalid new RDN '$newrdn'"
00500 }
00501
00502 set olddn [$self dn]
00503 if {! [regexp {^([^=]+)=([^,]+),(.*)} $olddn m oattr oval osup]} then {
00504 return -code "Invalid old DN '$olddn'"
00505 }
00506
00507 if {$newsup eq ""} then {
00508 set dn "$newrdn,$osup"
00509 } else {
00510 set dn "$newrdn,$newsup"
00511 }
00512 $self dn $dn
00513
00514 if {$delold} then {
00515 $self del1 $oattr $oval
00516 }
00517
00518 # XXX should we ignore case ?
00519 if {[lsearch -exact [$self get $nattr] $nval] == -1} then {
00520 $self add1 $nattr $nval
00521 }
00522 }
00523 default {
00524 return -code error \
00525 "Invalid change operation '$op'"
00526 }
00527 }
00528 }
00529 }
00530
00531
00532
00533
00534
00535
00536
00537 ret change (optional newchg ={-)} {
00538
00539 $self compatible "change"
00540
00541 if {$newchg ne "-"} then {
00542 change = $newchg
00543 }
00544 return $change
00545 }
00546
00547
00548
00549
00550
00551
00552 ret diff (type new , optional old ={)} {
00553
00554 $self compatible "change"
00555
00556 #
00557 # Select where backup is. If internal, creates a temporary
00558 # standard entry.
00559 #
00560
00561 if {$old eq ""} then {
00562 destroy = _old 1
00563 old = [::ldapx::entry create %AUTO%]
00564 $new restore $old
00565 } else {
00566 destroy = _old 0
00567 }
00568
00569
00570
00571
00572
00573 if {[$old dn] ne ""} then {
00574 $self dn [$old dn]
00575 } elseif {[$new dn] ne ""} then {
00576 $self dn [$new dn]
00577 } else {
00578 $self dn ""
00579 }
00580
00581 switch -- "[$new isempty][$old isempty]" {
00582 00 {
00583
00584 change = [DiffEntries $new $old]
00585 }
00586 01 {
00587
00588 change = [list [list "add" [$new getall]]]
00589 }
00590 10 {
00591
00592 change = [list [list "del"]]
00593 }
00594 11 {
00595
00596 change = {}
00597 }
00598 }
00599
00600
00601
00602
00603
00604 if {$destroy_old} then {
00605 $old destroy
00606 }
00607
00608 return $change
00609 }
00610
00611
00612
00613 ret DiffEntries (type new , type old) {
00614 array set tnew [$new getall]
00615 array set told [$old getall]
00616
00617 set lmod {}
00618
00619 #
00620 # First step : is there a DN change?
00621 #
00622
00623 set moddn [DiffDn [$new dn] [$old dn] tnew told]
00624
00625 #
00626 # Second step : pick up changes in attributes and/or values
00627 #
00628
00629 foreach a [array names tnew] {
00630 if {[info exists told($a)]} then {
00631 #
00632 # They are new and old values for this attribute.
00633 # We cannot use individual delete or add (rfc 4512,
00634 # paragraph 2.5.1) for attributes which do not have an
00635 # equality operator, so we use "replace" everywhere.
00636 #
00637
00638 set lnew [lsort $tnew($a)]
00639 set lold [lsort $told($a)]
00640 if {$lold ne $lnew} then {
00641 lappend lmod [list "modrepl" $a $tnew($a)]
00642 }
00643
00644 unset tnew($a)
00645 unset told($a)
00646 } else {
00647 lappend lmod [list "modadd" $a $tnew($a)]
00648 unset tnew($a)
00649 }
00650 }
00651
00652 foreach a [array names told] {
00653 lappend lmod [list "moddel" $a]
00654 }
00655
00656 set lchg {}
00657
00658 if {[llength $lmod]} then {
00659 lappend lchg [list "mod" $lmod]
00660 }
00661
00662 #
00663 # Third step : insert modDN changes
00664 #
00665
00666 if {[llength $moddn]} then {
00667 set newrdn [lindex $moddn 0]
00668 set deleteoldrdn [lindex $moddn 1]
00669 set newsuperior [lindex $moddn 2]
00670
00671 set lmod [list "modrdn" $newrdn $deleteoldrdn]
00672 if {! [string equal $newsuperior ""]} then {
00673 lappend lmod $newsuperior
00674 }
00675 lappend lchg $lmod
00676 }
00677
00678 return $lchg
00679 }
00680
00681 ret DiffDn (type newdn , type olddn _, type tnew _, type told) {
00682 upvar $_tnew tnew
00683 upvar $_told told
00684
00685 #
00686 # If DNs are the same, exit
00687 #
00688
00689 if {[string equal -nocase $newdn $olddn]} then {
00690 return {}
00691 }
00692
00693 #
00694 # Split components of both DNs : attribute, value, superior
00695 #
00696
00697 if {! [regexp {^([^=]+)=([^,]+),(.*)} $olddn m oattr oval osup]} then {
00698 return -code "Invalid old DN '$olddn'"
00699 }
00700 set oattr [string tolower $oattr]
00701 set ordn "$oattr=$oval"
00702
00703 if {! [regexp {^([^=]+)=([^,]+),(.*)} $newdn m nattr nval nsup]} then {
00704 return -code "Invalid new DN '$newdn'"
00705 }
00706 set nattr [string tolower $nattr]
00707 set nrdn "$nattr=$nval"
00708
00709 #
00710 # Checks if superior has changed
00711 #
00712
00713 if {! [string equal -nocase $osup $nsup]} then {
00714 set newsuperior $nsup
00715 } else {
00716 set newsuperior ""
00717 }
00718
00719 #
00720 # Checks if rdn has changed
00721 #
00722
00723 if {! [string equal -nocase $ordn $nrdn]} then {
00724 #
00725 # Checks if old rdn must be deleted
00726 #
00727
00728 set deleteoldrdn 1
00729 if {[info exists tnew($oattr)]} then {
00730 set pos [lsearch -exact [string tolower $tnew($oattr)] \
00731 [string tolower $oval]]
00732 if {$pos != -1} then {
00733 set deleteoldrdn 0
00734 }
00735 }
00736
00737 #
00738 # Remove old and new rdn such as DiffEntries doesn't
00739 # detect any modification.
00740 #
00741
00742 foreach t {tnew told} {
00743 foreach {a v} [list $oattr $oval $nattr $nval] {
00744 if {[info exists ${t}($a)]} then {
00745 set l [set ${t}($a)]
00746 set pos [lsearch -exact [string tolower $l] \
00747 [string tolower $v] ]
00748 if {$pos != -1} then {
00749 set l [lreplace $l $pos $pos]
00750 if {[llength $l]} then {
00751 set ${t}($a) $l
00752 } else {
00753 unset -nocomplain ${t}($a)
00754 }
00755 }
00756 }
00757 }
00758 }
00759 } else {
00760 set deleteoldrdn 0
00761 }
00762
00763 return [list $nrdn $deleteoldrdn $newsuperior]
00764 }
00765
00766
00767
00768
00769
00770 }
00771
00772
00773
00774
00775
00776 snit::type ::ldapx::utf8trans {
00777
00778
00779
00780
00781
00782 option -utf8 -default {{.*} {}}
00783
00784
00785
00786
00787
00788 ret must (type attr) {
00789 set utf8yes [lindex $options(-utf8) 0]
00790 set utf8no [lindex $options(-utf8) 1]
00791 set r 0
00792 if {[regexp -expanded -nocase "^$utf8yes$" $attr]} then {
00793 set r 1
00794 if {[regexp -expanded -nocase "^$utf8no$" $attr]} then {
00795 set r 0
00796 }
00797 }
00798 return $r
00799 }
00800
00801 ret encode (type attr , type val) {
00802 if {[$self must $attr]} then {
00803 set val [encoding convertto utf-8 $val]
00804 }
00805 return $val
00806 }
00807
00808 ret decode (type attr , type val) {
00809 if {[$self must $attr]} then {
00810 set val [encoding convertfrom utf-8 $val]
00811 }
00812 return $val
00813 }
00814
00815 ret encodepairs (type avpairs) {
00816 set r {}
00817 foreach {attr vals} $avpairs {
00818 if {[llength $vals]} then {
00819 lappend r $attr [$self encode $attr $vals]
00820 } else {
00821 lappend r $attr
00822 }
00823 }
00824 return $r
00825 }
00826
00827 ret decodepairs (type avpairs) {
00828 set r {}
00829 foreach {attr vals} $avpairs {
00830 set vals [$self decode $attr $vals]
00831 lappend r $attr $vals
00832 }
00833 return $r
00834 }
00835 }
00836
00837
00838
00839
00840
00841 snit::type ::ldapx::ldap {
00842
00843
00844
00845
00846
00847
00848 option -scope -default "sub"
00849 option -derefaliases -default "never"
00850 option -sizelimit -default 0
00851 option -timelimit -default 0
00852 option -attrsonly -default 0
00853
00854 component translator
00855 delegate option -utf8 to translator
00856
00857
00858
00859
00860
00861 variable channel ""
00862 variable bind 0
00863
00864
00865
00866
00867
00868 variable lastError ""
00869
00870
00871
00872
00873
00874 variable connect_defaults -array {
00875 ldap {389 ::ldap::connect}
00876 ldaps {636 ::ldap::secure_connect}
00877 }
00878
00879
00880
00881
00882
00883
00884 constructor {args} {
00885 install translator using ::ldapx::utf8trans create %AUTO%
00886 $self configurelist $args
00887 }
00888
00889 destructor {
00890 catch {$translator destroy}
00891 }
00892
00893
00894
00895
00896
00897
00898
00899 ret error (optional le ={-)} {
00900
00901 if {! [string equal $le "-"]} then {
00902 lastError = $le
00903 }
00904 return $lastError
00905 }
00906
00907
00908
00909 ret connect (type url , optional binddn ={) {bindpw {}}} {
00910
00911 array comp = [::uri::split $url "ldap"]
00912
00913 if {! [::info exists comp(host)]} then {
00914 $self error "Invalid host in URL '$url'"
00915 return 0
00916 }
00917
00918 scheme = $comp(scheme)
00919 if {! [::info exists connect_defaults($scheme)]} then {
00920 $self error "Unrecognized URL '$url'"
00921 return 0
00922 }
00923
00924 defport = [lindex $connect_defaults($scheme) 0]
00925 fct = [lindex $connect_defaults($scheme) 1]
00926
00927 if {[string equal $comp(port) ""]} then {
00928 comp = (port) $defport
00929 }
00930
00931 if {[Check $selfns { channel = [$fct $comp(host) $comp(port)]}]} then {
00932 return 0
00933 }
00934
00935 if {$binddn eq ""} then {
00936 bind = 0
00937 } else {
00938 bind = 1
00939 if {[Check $selfns {::ldap::bind $channel $binddn $bindpw}]} then {
00940 return 0
00941 }
00942 }
00943 return 1
00944 }
00945
00946
00947
00948 ret disconnect () {
00949
00950 Connected $selfns
00951
00952 if {$bind} {
00953 if {[Check $selfns {::ldap::unbind $channel}]} then {
00954 return 0
00955 }
00956 }
00957 if {[Check $selfns {::ldap::disconnect $channel}]} then {
00958 return 0
00959 }
00960 set channel ""
00961 return 1
00962 }
00963
00964
00965
00966
00967 ret traverse (type base , type filter , type attrs , type entry , type body) {
00968
00969 Connected $selfns
00970
00971 global errorInfo errorCode
00972
00973 set lastError ""
00974
00975 #
00976 # Initiate search
00977 #
00978
00979 set opt [list \
00980 -scope $options(-scope) \
00981 -derefaliases $options(-derefaliases) \
00982 -sizelimit $options(-sizelimit) \
00983 -timelimit $options(-timelimit) \
00984 -attrsonly $options(-attrsonly) \
00985 ]
00986
00987 ::ldap::searchInit $channel $base $filter $attrs $opt
00988
00989 #
00990 # Execute the specific body for each result found
00991 #
00992
00993 while {1} {
00994 #
00995 # The first call to searchNext may fail when searchInit
00996 # is given some invalid parameters.
00997 # We must terminate the current search in order to allow
00998 # future searches.
00999 #
01000
01001 set err [catch {::ldap::searchNext $channel} r]
01002
01003 if {$err} then {
01004 set ei $errorInfo
01005 set ec $errorCode
01006 ::ldap::searchEnd $channel
01007 return -code error -errorinfo $ei -errorcode $ec $r
01008 }
01009
01010 #
01011 # End of result messages
01012 #
01013
01014 if {[llength $r] == 0} then {
01015 break
01016 }
01017
01018 #
01019 # Set DN and attributes-values (converted from utf8 if needed)
01020 # for the entry
01021 #
01022
01023 $entry reset
01024
01025 $entry dn [lindex $r 0]
01026 $entry setall [$translator decodepairs [lindex $r 1]]
01027
01028 #
01029 # Execute body with the entry
01030 #
01031 # http:
01032 #
01033
01034 set code [catch {uplevel 1 $body} msg]
01035 switch -- $code {
01036 0 {
01037 # ok
01038 }
01039 1 {
01040 # error
01041 set ei $errorInfo
01042 set ec $errorCode
01043 ::ldap::searchEnd $channel
01044 return -code error -errorinfo $ei -errorcode $ec $msg
01045 }
01046 2 {
01047 # return
01048 ::ldap::searchEnd $channel
01049 return -code return $msg
01050 }
01051 3 {
01052 # break
01053 ::ldap::searchEnd $channel
01054 return {}
01055 }
01056 4 {
01057 # continue
01058 }
01059 default {
01060 # user defined
01061 ::ldap::searchEnd $channel
01062 return -code $code $msg
01063 }
01064 }
01065 }
01066
01067 #
01068 # Terminate search
01069 #
01070
01071 ::ldap::searchEnd $channel
01072 }
01073
01074
01075
01076 ret search (type base , type filter , type attrs) {
01077
01078 Connected $selfns
01079
01080 set e [::ldapx::entry create %AUTO%]
01081 set r {}
01082 $self traverse $base $filter $attrs $e {
01083 set new [::ldapx::entry create %AUTO%]
01084 $e backup $new
01085 lappend r $new
01086 }
01087 $e destroy
01088 return $r
01089 }
01090
01091
01092
01093
01094 ret read (type base , type filter , type args) {
01095
01096 set n 0
01097 set max [llength $args]
01098 set e [::ldapx::entry create %AUTO%]
01099 $self traverse $base $filter {} $e {
01100 if {$n < $max} then {
01101 $e backup [lindex $args $n]
01102 }
01103 incr n
01104 }
01105 return $n
01106 }
01107
01108
01109
01110 ret commit (type args) {
01111
01112 Connected $selfns
01113
01114 foreach entry $args {
01115 switch -- [$entry format] {
01116 uninitialized {
01117 return -code error \
01118 "Uninitialized entry"
01119 }
01120 standard {
01121 set echg [::ldapx::entry create %AUTO%]
01122 set lchg [$echg diff $entry]
01123 set dn [$echg dn]
01124 $echg destroy
01125 }
01126 change {
01127 set dn [$entry dn]
01128 set lchg [$entry change]
01129 }
01130 }
01131
01132 foreach chg $lchg {
01133 set op [lindex $chg 0]
01134
01135 switch -- $op {
01136 {} {
01137 # nothing to do
01138 }
01139 add {
01140 set av [$translator encodepairs [lindex $chg 1]]
01141 if {[Check $selfns {::ldap::addMulti $channel $dn $av}]} then {
01142 return 0
01143 }
01144 }
01145 del {
01146 if {[Check $selfns {::ldap::delete $channel $dn}]} then {
01147 return 0
01148 }
01149 }
01150 mod {
01151 set lrep {}
01152 set ldel {}
01153 set ladd {}
01154
01155 foreach submod [lindex $chg 1] {
01156 set subop [lindex $submod 0]
01157 set attr [lindex $submod 1]
01158 set vals [lindex $submod 2]
01159
01160 set vals [$translator encode $attr $vals]
01161 switch -- $subop {
01162 modadd {
01163 lappend ladd $attr $vals
01164 }
01165 moddel {
01166 lappend ldel $attr $vals
01167 }
01168 modrepl {
01169 lappend lrep $attr $vals
01170 }
01171 }
01172 }
01173
01174 if {[Check $selfns {::ldap::modifyMulti $channel $dn \
01175 $lrep $ldel $ladd}]} then {
01176 return 0
01177 }
01178 }
01179 modrdn {
01180 set newrdn [lindex $chg 1]
01181 set delOld [lindex $chg 2]
01182 set newSup [lindex $chg 3]
01183 if {[string equal $newSup ""]} then {
01184 if {[Check $selfns {::ldap::modifyDN $channel $dn \
01185 $newrdn $delOld}]} then {
01186 return 0
01187 }
01188 } else {
01189 if {[Check $selfns {::ldap::modifyDN $channel $dn \
01190 $newrdn $delOld $newSup}]} then {
01191 return 0
01192 }
01193 }
01194 }
01195 }
01196 }
01197 }
01198
01199 return 1
01200 }
01201
01202
01203
01204
01205
01206 ret Connected (type selfns) {
01207 if {$channel eq ""} then {
01208 return -code error \
01209 "Object not connected"
01210 }
01211 }
01212
01213 ret Check (type selfns , type script) {
01214 return [catch {uplevel 1 $script} lastError]
01215 }
01216
01217
01218
01219
01220 }
01221
01222
01223
01224
01225
01226 snit::type ::ldapx::ldif {
01227
01228
01229
01230
01231
01232
01233
01234
01235
01236 option -ignore {}
01237
01238 component translator
01239 delegate option -utf8 to translator
01240
01241
01242
01243
01244
01245
01246
01247
01248
01249
01250 variable version 0
01251
01252
01253
01254
01255
01256 variable channel ""
01257
01258
01259
01260
01261
01262 variable lineno 0
01263
01264
01265
01266
01267
01268 variable lastError ""
01269
01270
01271
01272
01273
01274 variable nentries 0
01275
01276
01277
01278
01279
01280 variable format "uninitialized"
01281
01282
01283
01284
01285
01286 constructor {args} {
01287 install translator using ::ldapx::utf8trans create %AUTO%
01288 $self configurelist $args
01289 }
01290
01291 destructor {
01292 catch {$translator destroy}
01293 }
01294
01295
01296
01297
01298
01299
01300
01301 ret channel (type newchan) {
01302
01303 set channel $newchan
01304 set version 0
01305 set nentries 0
01306 set format "uninitialized"
01307 set lineno 0
01308 return
01309 }
01310
01311
01312
01313 ret error (optional le ={-)} {
01314
01315 if {$le ne "-"} then {
01316 lastError = $le
01317 }
01318 return $lastError
01319 }
01320
01321
01322
01323
01324 ret compatible (type ref) {
01325
01326 if {$format eq "uninitialized"} then {
01327 set format $ref
01328 } elseif {$format ne $ref} then {
01329 return -code error \
01330 "Invalid entry ($ref) type for LDIF $format file"
01331 }
01332 }
01333
01334
01335
01336
01337
01338
01339 ret debugread (type entry) {
01340
01341 $entry compatible "standard"
01342 $entry dn "uid=joe,ou=org,o=com"
01343 $entry setall {uid {joe} sn {User} givenName {Joe} cn {{Joe User}}
01344 telephoneNumber {+31415926535 +27182818285} objectClass {person}
01345 }
01346 return 1
01347 }
01348
01349
01350
01351
01352 ret read (type entry) {
01353 if {$channel eq ""} then {
01354 return -code error \
01355 "Channel not initialized"
01356 }
01357
01358 set r [Lexical $selfns]
01359 if {[lindex $r 0] ne "err"} then {
01360 set r [Syntaxic $selfns [lindex $r 1]]
01361 }
01362
01363 if {[lindex $r 0] eq "err"} then {
01364 set lastError [lindex $r 1]
01365 return 0
01366 }
01367
01368 switch -- [lindex $r 0] {
01369 uninitialized {
01370 $entry reset
01371 set lastError ""
01372 set r 0
01373 }
01374 standard {
01375 if {[catch {$self compatible "change"}]} then {
01376 set lastError "Standard entry not allowed in LDIF change file"
01377 set r 0
01378 } else {
01379 $entry reset
01380 $entry dn [lindex $r 1]
01381 $entry setall [lindex $r 2]
01382 set r 1
01383 }
01384 }
01385 change {
01386 if {[catch {$self compatible "change"}]} then {
01387 set lastError "Change entry not allowed in LDIF standard file"
01388 set r 0
01389 } else {
01390 $entry reset
01391 $entry dn [lindex $r 1]
01392 $entry change [list [lindex $r 2]]
01393 set r 1
01394 }
01395 }
01396 default {
01397 return -code error \
01398 "Internal error (invalid returned entry format)"
01399 }
01400 }
01401
01402 return $r
01403 }
01404
01405
01406
01407 ret write (type entry) {
01408
01409 if {$channel eq ""} then {
01410 return -code error \
01411 "Channel not initialized"
01412 }
01413
01414 switch -- [$entry format] {
01415 uninitialized {
01416 # nothing
01417 }
01418 standard {
01419 if {[llength [$entry getall]]} then {
01420 $self compatible "standard"
01421
01422 if {$nentries == 0} then {
01423 if {$version == 0} then {
01424 set version 1
01425 }
01426 WriteLine $selfns "version" "$version"
01427 puts $channel ""
01428 }
01429
01430 WriteLine $selfns "dn" [$entry dn]
01431
01432 foreach a [$entry getattr] {
01433 foreach v [$entry get $a] {
01434 WriteLine $selfns $a $v
01435 }
01436 }
01437 puts $channel ""
01438 }
01439 }
01440 change {
01441 $self compatible "change"
01442
01443 set lchg [$entry change]
01444 foreach chg $lchg {
01445 if {$nentries == 0} then {
01446 if {$version == 0} then {
01447 set version 1
01448 }
01449 WriteLine $selfns "version" "$version"
01450 puts $channel ""
01451 }
01452
01453 WriteLine $selfns "dn" [$entry dn]
01454
01455 set op [lindex $chg 0]
01456 switch -- $op {
01457 add {
01458 WriteLine $selfns "changetype" "add"
01459 foreach {attr vals} [lindex $chg 1] {
01460 foreach v $vals {
01461 WriteLine $selfns $attr $v
01462 }
01463 }
01464 }
01465 del {
01466 WriteLine $selfns "changetype" "delete"
01467 }
01468 mod {
01469 WriteLine $selfns "changetype" "modify"
01470 foreach submod [lindex $chg 1] {
01471 set subop [lindex $submod 0]
01472 set attr [lindex $submod 1]
01473 set vals [lindex $submod 2]
01474
01475 switch -- $subop {
01476 modadd {
01477 WriteLine $selfns "add" $attr
01478 }
01479 moddel {
01480 WriteLine $selfns "delete" $attr
01481 }
01482 modrepl {
01483 WriteLine $selfns "replace" $attr
01484 }
01485 }
01486 foreach v $vals {
01487 WriteLine $selfns $attr $v
01488 }
01489 puts $channel "-"
01490 }
01491 }
01492 modrdn {
01493 WriteLine $selfns "changetype" "modrdn"
01494 set newrdn [lindex $chg 1]
01495 set delold [lindex $chg 2]
01496 set newsup [lindex $chg 3]
01497 WriteLine $selfns "newrdn" $newrdn
01498 WriteLine $selfns "deleteOldRDN" $delold
01499 if {$newsup ne ""} then {
01500 WriteLine $selfns "newSuperior" $newsup
01501 }
01502 }
01503 }
01504 puts $channel ""
01505 incr nentries
01506 }
01507 }
01508 default {
01509 return -code error \
01510 "Invalid entry format"
01511 }
01512 }
01513 return 1
01514 }
01515
01516
01517
01518
01519
01520
01521
01522
01523
01524
01525
01526
01527 ret Lexical (type selfns) {
01528 set result {}
01529 set prev ""
01530
01531 while {[gets $channel line] > -1} {
01532 incr lineno
01533
01534 if {$line eq ""} then {
01535 #
01536 # Empty line: we are either before the beginning
01537 # of the entry or at the empty line after the
01538 # entry.
01539 # We don't give up before getting something.
01540 #
01541
01542 if {! [FlushLine $selfns "" result prev msg]} then {
01543 return [list "err" $msg]
01544 }
01545
01546 if {[llength $result]} then {
01547 break
01548 }
01549
01550 } elseif {[regexp {^[ \t]} $line]} then {
01551 #
01552 # Continuation line
01553 #
01554
01555 append prev [string trim $line]
01556
01557 } elseif {[regexp {^-$} $line]} then {
01558 #
01559 # Separation between individual modifications
01560 #
01561
01562 if {! [FlushLine $selfns "" result prev msg]} then {
01563 return [list "err" $msg]
01564 }
01565 lappend result [list "-" {}]
01566
01567 } else {
01568 #
01569 # Should be a normal line (key: val)
01570 #
01571
01572 if {! [FlushLine $selfns $line result prev msg]} then {
01573 return [list "err" $msg]
01574 }
01575
01576 }
01577 }
01578
01579 #
01580 # End of file, or end of entry. Flush buffered data from $prev
01581 # for EOF case.
01582 #
01583
01584 if {! [FlushLine $selfns "" result prev msg]} then {
01585 return [list "err" $msg]
01586 }
01587
01588 return [list "ok" $result]
01589 }
01590
01591 ret FlushLine (type selfns , type line _, type result _, type prev _, type msg) {
01592 upvar $_result result $_prev prev $_msg msg
01593
01594 if {$prev ne ""} then {
01595 set r [DecodeLine $selfns $prev]
01596 if {[llength $r] != 2} then {
01597 set msg "$lineno: invalid syntax"
01598 return 0
01599 }
01600
01601 #
01602 # Special case for "version: 1". This code should not
01603 # be in lexical analysis, but this would be too disruptive
01604 # in syntaxic analysis
01605 #
01606
01607 if {[string equal -nocase [lindex $r 0] "version"]} then {
01608 if {$version != 0} then {
01609 set msg "version attribute allowed only at the beginning of the LDIF file"
01610 return 0
01611 }
01612 set val [lindex $r 1]
01613 if {[catch {set val [expr {$val+0}]}]} then {
01614 set msg "invalid version value"
01615 return 0
01616 }
01617 if {$val != 1} then {
01618 set msg "unrecognized version '$val'"
01619 return 0
01620 }
01621 set version 1
01622 } else {
01623 lappend result $r
01624 }
01625 }
01626 set prev $line
01627
01628 return 1
01629 }
01630
01631 ret DecodeLine (type selfns , type str) {
01632 if {[regexp {^([^:]*)::[ \t]*(.*)} $str d key val]} then {
01633 set key [string tolower $key]
01634 set val [::base64::decode $val]
01635 set val [$translator decode $key $val]
01636 set r [list $key $val]
01637 } elseif {[regexp {^([^:]*):[ \t]*(.*)} $str d key val]} then {
01638 set key [string tolower $key]
01639 set val [$translator decode $key $val]
01640 set r [list $key $val]
01641 } else {
01642 # syntax error
01643 set r {}
01644 }
01645 return $r
01646 }
01647
01648
01649
01650
01651
01652
01653
01654
01655
01656 variable ldifautomaton -array {
01657 begin {
01658 {dn:* dn { dn = $val}}
01659 {EOF:* end { r = [list "empty"]}}
01660 }
01661 dn {
01662 {changetype:modify mod { t = "change" ; r = {mod}}}
01663 {changetype:modrdn modrdn { t = "change" ; newsup = {}}}
01664 {changetype:add add { t = "change"}}
01665 {changetype:delete del { t = "change"}}
01666 {*:* standard { t = "standard" ; lappend tab($key) $val}}
01667 }
01668 standard {
01669 {EOF:* end { r = [array get tab]}}
01670 {*:* standard {lappend tab($key) $val}}
01671 }
01672 mod {
01673 {add:* mod-add { attr = [string tolower $val] ; vals = {}}}
01674 {delete:* mod-del { attr = [string tolower $val] ; vals = {}}}
01675 {replace:* mod-repl { attr = [string tolower $val] ; vals = {}}}
01676 {EOF:* end {}}
01677 }
01678 mod-add {
01679 {*:* mod-add-attr {lappend vals $val}}
01680 }
01681 mod-add-attr {
01682 {-:* mod {lappend r [list "modadd" $attr $vals]}}
01683 {*:* mod-add-attr {lappend vals $val}}
01684 }
01685 mod-del {
01686 {-:* mod {lappend r [list "moddel" $attr $vals]}}
01687 {*:* mod-del {lappend vals $val}}
01688 }
01689 mod-repl {
01690 {-:* mod {lappend r [list "modrepl" $attr $vals]}}
01691 {*:* mod-repl {lappend vals $val}}
01692 }
01693 modrdn {
01694 {newrdn:* modrdn-new { newrdn = $val}}
01695 }
01696 modrdn-new {
01697 {deleteoldrdn:0 modrdn-del { delold = 0}}
01698 {deleteoldrdn:1 modrdn-del { delold = 1}}
01699 }
01700 modrdn-del {
01701 {newsuperior:* modrdn-end { newsup = $val}}
01702 {EOF:* end { r = [list modrdn $newrdn $delold] }}
01703 }
01704 modrdn-end {
01705 {EOF:* end { r = [list modrdn $newrdn
01706 $delold $newsup]}}
01707 }
01708 add {
01709 {EOF:* end { r = [list add [array get tab]]}}
01710 {*:* add {lappend tab($key) $val}}
01711 }
01712 del {
01713 {EOF:* end { r = [list del]}}
01714 }
01715 }
01716
01717 ret Syntaxic (type selfns , type lcouples) {
01718 set state "begin"
01719 set newsup {}
01720 set t "uninitialized"
01721 foreach c $lcouples {
01722 set key [lindex $c 0]
01723 if {[lsearch [string tolower $options(-ignore)] $key] == -1} then {
01724 set val [lindex $c 1]
01725 set a [Automaton $selfns $state $key $val]
01726 if {$a eq ""} then {
01727 return [list "err" "Syntax error before line $lineno"]
01728 }
01729 set state [lindex $a 0]
01730 set script [lindex $a 1]
01731 eval $script
01732 }
01733 }
01734
01735 set a [Automaton $selfns $state "EOF" "EOF"]
01736 if {$a eq ""} then {
01737 return [list "err" "Premature EOF"]
01738 }
01739 set script [lindex $a 1]
01740 eval $script
01741
01742 set result [list $t]
01743 switch $t {
01744 uninitialized {
01745 # nothing
01746 }
01747 standard {
01748 lappend result $dn $r
01749 }
01750 change {
01751 lappend result $dn $r
01752 }
01753 }
01754
01755 return $result
01756 }
01757
01758 ret Automaton (type selfns , type state , type key , type val) {
01759 set r {}
01760 if {[info exists ldifautomaton($state)]} then {
01761 foreach a $ldifautomaton($state) {
01762 if {[string match [lindex $a 0] "$key:$val"]} then {
01763 set r [lreplace $a 0 0]
01764 break
01765 }
01766 }
01767 }
01768 return $r
01769 }
01770
01771
01772
01773
01774
01775 ret WriteLine (type selfns , type attr , type val) {
01776
01777 if {[string is ascii $val] && [string is print $val]} then {
01778 set sep ":"
01779 } else {
01780 set sep "::"
01781 set val [$translator encode $attr $val]
01782 set val [::base64::encode $val]
01783 }
01784
01785 set first 1
01786 foreach line [split $val "\n"] {
01787 if {$first} then {
01788 puts $channel "$attr$sep $line"
01789 set first 0
01790 } else {
01791 puts $channel " $line"
01792 }
01793 }
01794 }
01795 }
01796