ldapx.tcl

Go to the documentation of this file.
00001 /* */
00002 /*  Extended object interface to entries in LDAP directories or LDIF files.*/
00003 /* */
00004 /*  (c) 2006 Pierre David (pdav@users.sourceforge.net)*/
00005 /* */
00006 /*  $Id: ldapx.tcl,v 1.11 2007/08/19 20:20:43 pdav Exp $*/
00007 /* */
00008 /*  History:*/
00009 /*    2006/08/08 : pda : design*/
00010 /* */
00011 
00012 package require Tcl 8.4
00013 package require snit        ;/*  tcllib*/
00014 package require uri 1.1.5   ;/*  tcllib*/
00015 package require base64      ;/*  tcllib*/
00016 package require ldap 1.6    ;/*  tcllib, low level code for LDAP directories*/
00017 
00018 package provide ldapx 1.0
00019 
00020 /* */
00021 /*  LDAPENTRY object type*/
00022 /* */
00023 
00024 snit::type ::ldapx::entry {
00025     /* */
00026     /*  Variables*/
00027     /* */
00028 
00029     /* */
00030     /*  Format of an individual entry*/
00031     /*  May be "standard" (standard LDAP entry, read from an LDAP directory*/
00032     /*  or from a LDIF channel) or "change" (LDIF change, or result of the*/
00033     /*  comparison of two standard entries).*/
00034     /*  Special : "uninitialized" means that this entry has not been used,*/
00035     /*  and the first use will initialize it.*/
00036     /* */
00037 
00038     variable format "uninitialized"
00039 
00040     /* */
00041     /*  DN*/
00042     /* */
00043 
00044     variable dn ""
00045 
00046     /* */
00047     /*  Standard entry*/
00048     /* */
00049     /*  Syntax:*/
00050     /*    - array indexed by attribute names (lower case)*/
00051     /*    - each value is the list of attributes*/
00052     /* */
00053     /*  The current state may be backed up in an internal state.*/
00054     /*  (see backup and restore methods)*/
00055     /* */
00056 
00057     variable attrvals -array {}
00058 
00059     variable backup 0
00060     variable bckav  -array {}
00061     variable bckdn  ""
00062 
00063     /* */
00064     /*  Change entry*/
00065     /* */
00066     /*  Syntax:*/
00067     /*  {{<op> <parameters>} ... }*/
00068     /*      if <op> = mod*/
00069     /*      {mod {{<modop> <attr> [ {<val1> ... <valn>} ]} ...} }*/
00070     /*      where <modop> = modrepl, modadd, moddel*/
00071     /*      if <op> = add*/
00072     /*      {add {<attr> {<val1> ... <valn>} ...}}*/
00073     /*      if <op> = del*/
00074     /*      {del}*/
00075     /*      if <op> = modrdn*/
00076     /*      {modrdn <newrdn> <deleteoldrdn> [ <newsuperior> ]}*/
00077     /* */
00078 
00079     variable change ""
00080 
00081     /* */
00082     /*  Generic methods (for both standard and change entries)*/
00083     /* */
00084 
00085     /*  Resets the entry to an empty state*/
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     /*  Returns current format*/
00099 
00100     ret  format () {
00101 
00102     return $format
00103     }
00104 
00105     /*  Checks if entry is compatible with a certain format*/
00106     /*  errors out if not*/
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     /*  Get or set the current dn*/
00119 
00120     ret  dn (optional newdn ={-)} {
00121 
00122     if {$newdn ne "-"} then {
00123          dn =  $newdn
00124     }
00125     return $dn
00126     }
00127 
00128     /*  Get the "superior" (LDAP slang word) part of current dn*/
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     /*  Get the "rdn" part of current dn*/
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     /*  Get a printable form of the contents*/
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     /*  Prints the whole state of an entry*/
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     /*  Methods for standard entries*/
00219     /* */
00220 
00221     /*  Tells if the current entry is empty*/
00222 
00223     ret  isempty () {
00224 
00225     $self compatible "standard"
00226 
00227     return [expr {[array size attrvals] == 0}]
00228     }
00229 
00230     /*  Get all values for an attribute*/
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     /*  Get only the first value for an attribute*/
00246 
00247     ret  get1 (type attr) {
00248 
00249     return [lindex [$self get $attr] 0]
00250     }
00251 
00252 
00253     /*  Set all values for an attribute*/
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     /*  Set only one value for an attribute*/
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     /*  Add some values to an attribute*/
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     /*  Add only one value to an attribute*/
00295 
00296     ret  add1 (type attr , type val) {
00297 
00298     return [$self add $attr [list $val]]
00299     }
00300 
00301     /*  Delete all values (or some values only) for an attribute*/
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     /*  Delete only one value from an attribute*/
00328 
00329     ret  del1 (type attr , type val) {
00330 
00331     $self del $attr [list $val]
00332     }
00333 
00334     /*  Get all attribute names*/
00335 
00336     ret  getattr () {
00337 
00338     $self compatible "standard"
00339 
00340     return [array names attrvals]
00341     }
00342 
00343     /*  Get all attribute names and values*/
00344 
00345     ret  getall () {
00346 
00347     $self compatible "standard"
00348 
00349     return [array get attrvals]
00350     }
00351 
00352     /*  Reset all attribute names and values at once*/
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     /*  Back up current entry into a new one or into the internal backup state*/
00366 
00367     ret  backup (optional other ={)} {
00368 
00369     $self compatible "standard"
00370 
00371     if {$other eq ""} then {
00372         /* */
00373         /*  Back-up entry in $self->$oldav and $self->$dn*/
00374         /* */
00375          backup =  1
00376          bckdn =  $dn
00377 
00378         array un bckav = 
00379         array  bckav =  [array get attrvals]
00380     } else {
00381         /* */
00382         /*  Back-up entry in $other*/
00383         /* */
00384         $other compatible "standard"
00385         $other dn $dn
00386         $other all =  [array get attrvals]
00387     }
00388     }
00389 
00390     /*  Restore current entry from an old one or from the internal backup state*/
00391 
00392     ret  restore (optional other ={)} {
00393 
00394     $self compatible "standard"
00395 
00396     if {$backup} then {
00397         if {$other eq ""} then {
00398         /* */
00399         /*  Restore in current context*/
00400         /* */
00401          dn =  $bckdn
00402         array un attrvals = 
00403         array  attrvals =  [array get bckav]
00404         } else {
00405         /* */
00406         /*  Restore in another object*/
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     /*  Swap current and backup data, if they reside in the same entry*/
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     /*  Apply some modifications (given by a change entry) to current entry*/
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     /*  Methods for change entries*/
00533     /* */
00534 
00535     /*  Get or set all modifications*/
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     /*  Compute the difference between two entries (or between an entry*/
00548     /*  and the backed-up internal state) into the current change entry*/
00549     /*  e1 : new, e2 : old*/
00550     /*  if e2 is not given, it defaults to backup in e1*/
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     /*  Computes differences between values in the two entries*/
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         /*  They may differ*/
00584          change =  [DiffEntries $new $old]
00585         }
00586         01 {
00587         /*  new has been added*/
00588          change =  [list [list "add" [$new getall]]]
00589         }
00590         10 {
00591         /*  new has been deleted*/
00592          change =  [list [list "del"]]
00593         }
00594         11 {
00595         /*  they are both empty: no change*/
00596          change =  {}
00597         }
00598     }
00599 
00600     /* */
00601     /*  Remove temporary standard entry (backup was internal)*/
00602     /* */
00603 
00604     if {$destroy_old} then {
00605         $old destroy
00606     }
00607 
00608     return $change
00609     }
00610 
00611     /*  local procedure to compute differences between two non empty entries*/
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     /*  End of ldapentry*/
00769     /* */
00770 }
00771 
00772 /* */
00773 /*  UTF8 translator, component used to manage the -utf8 option*/
00774 /* */
00775 
00776 snit::type ::ldapx::utf8trans {
00777 
00778     /* */
00779     /*  Option*/
00780     /* */
00781 
00782     option -utf8     -default {{.*} {}}
00783 
00784     /* */
00785     /*  Methods*/
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 /*  LDAP object type*/
00839 /* */
00840 
00841 snit::type ::ldapx::ldap {
00842     /* */
00843     /*  Options*/
00844     /* */
00845     /*  note : options are lowercase*/
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     /*  Channel descriptor*/
00859     /* */
00860 
00861     variable channel ""
00862     variable bind 0
00863 
00864     /* */
00865     /*  Last error*/
00866     /* */
00867 
00868     variable lastError ""
00869 
00870     /* */
00871     /*  Defaults connection modes*/
00872     /* */
00873 
00874     variable connect_defaults -array {
00875                     ldap {389 ::ldap::connect}
00876                     ldaps {636 ::ldap::secure_connect}
00877                 }
00878 
00879 
00880     /* */
00881     /*  Constructor*/
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     /*  Methods*/
00895     /* */
00896 
00897     /*  Get or set the last error message*/
00898 
00899     ret  error (optional le ={-)} {
00900 
00901     if {! [string equal $le "-"]} then {
00902          lastError =  $le
00903     }
00904     return $lastError
00905     }
00906 
00907     /*  Connect to the LDAP directory, and binds to it if needed*/
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     /*  Disconnect from the LDAP directory*/
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     /*  New control structure : traverse the DIT and execute the body*/
00965     /*  for each found entry.*/
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://wiki.tcl.tk/685
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     /*  Returns a list of newly created objects which match*/
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     /*  Read one or more entries, and returns the number of entries found.*/
01092     /*  Useful to easily read one or more entries.*/
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     /*  Commit a list of changes (or standard, backuped entries)*/
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     /*  Local procedures*/
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     /*  End of LDAP object type*/
01219     /* */
01220 }
01221 
01222 /* */
01223 /*  LDIF object type*/
01224 /* */
01225 
01226 snit::type ::ldapx::ldif {
01227 
01228     /* */
01229     /*  Options*/
01230     /* */
01231 
01232     /* */
01233     /*  Fields to ignore when reading change file*/
01234     /* */
01235 
01236     option -ignore {}
01237 
01238     component translator
01239     delegate option -utf8 to translator
01240 
01241 
01242     /* */
01243     /*  Variables*/
01244     /* */
01245 
01246     /* */
01247     /*  Version of LDIF file (0 means : uninitialized)*/
01248     /* */
01249 
01250     variable version 0
01251 
01252     /* */
01253     /*  Channel descriptor*/
01254     /* */
01255 
01256     variable channel ""
01257 
01258     /* */
01259     /*  Line number*/
01260     /* */
01261 
01262     variable lineno 0
01263 
01264     /* */
01265     /*  Last error message*/
01266     /* */
01267 
01268     variable lastError ""
01269 
01270     /* */
01271     /*  Number of entries read or written*/
01272     /* */
01273 
01274     variable nentries 0
01275 
01276     /* */
01277     /*  Type of LDIF file*/
01278     /* */
01279 
01280     variable format "uninitialized"
01281 
01282     /* */
01283     /*  Constructor*/
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     /*  Methods*/
01297     /* */
01298 
01299     /*  Initialize a channel*/
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     /*  Get or set the last error message*/
01312 
01313     ret  error (optional le ={-)} {
01314 
01315     if {$le ne "-"} then {
01316          lastError =  $le
01317     }
01318     return $lastError
01319     }
01320 
01321     /*  An LDIF file cannot include both changes and standard entries*/
01322     /*  (see RFC 2849, page 2). Check this.*/
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     /*  Reads an LDIF entry (standard or change) from the channel*/
01335     /*  returns 1 if ok, 0 if error or EOF*/
01336 
01337     /*  XXX this method is just coded for tests at this time*/
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     /*  Read an LDIF entry (standard or change) from the channel*/
01350     /*  returns 1 if ok, 0 if error or EOF*/
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     /*  Write an LDIF entry to the channel*/
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     /*  Local procedures to read an entry*/
01518     /* */
01519 
01520     /* */
01521     /*  Lexical analysis of an entry*/
01522     /*  Special case for "version:" entry.*/
01523     /*  Returns a list of lines {ok {{<attr1> <val1>} {<attr2> <val2>} ...}}*/
01524     /*  or a list {err <message>}*/
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     /*  Array indexed by current state of the LDIF automaton*/
01650     /*  Each element is a list of actions, each with the format:*/
01651     /*  pattern on on "attribute:value"*/
01652     /*  next state*/
01653     /*  script (to be evaled in Syntaxic local procedure)*/
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     /*  Local procedures to write an entry*/
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 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1