ldap.tcl

Go to the documentation of this file.
00001 /* -----------------------------------------------------------------------------*/
00002 /*    Copyright (C) 1999-2004 Jochen C. Loewer (loewerj@web.de)*/
00003 /*    Copyright (C) 2006      Michael Schlenker (mic42@users.sourceforge.net)    */
00004 /* -----------------------------------------------------------------------------*/
00005 /* */
00006 /*    A (partial) LDAPv3 protocol implementation in plain Tcl.*/
00007 /* */
00008 /*    See RFC 2251 and ASN.1 (X.680) and BER (X.690).*/
00009 /* */
00010 /* */
00011 /*    This software is copyrighted by Jochen C. Loewer (loewerj@web.de). The*/
00012 /*    following terms apply to all files associated with the software unless*/
00013 /*    explicitly disclaimed in individual files.*/
00014 /* */
00015 /*    The authors hereby grant permission to use, copy, modify, distribute,*/
00016 /*    and license this software and its documentation for any purpose, provided*/
00017 /*    that existing copyright notices are retained in all copies and that this*/
00018 /*    notice is included verbatim in any distributions. No written agreement,*/
00019 /*    license, or royalty fee is required for any of the authorized uses.*/
00020 /*    Modifications to this software may be copyrighted by their authors*/
00021 /*    and need not follow the licensing terms described here, provided that*/
00022 /*    the new terms are clearly indicated on the first page of each file where*/
00023 /*    they apply.*/
00024 /* */
00025 /*    IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY*/
00026 /*    FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES*/
00027 /*    ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY*/
00028 /*    DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE*/
00029 /*    POSSIBILITY OF SUCH DAMAGE.*/
00030 /* */
00031 /*    THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,*/
00032 /*    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,*/
00033 /*    FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE*/
00034 /*    IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE*/
00035 /*    NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR*/
00036 /*    MODIFICATIONS.*/
00037 /* */
00038 /*    $Id: ldap.tcl,v 1.23 2006/11/15 19:28:03 mic42 Exp $*/
00039 /* */
00040 /*    written by Jochen Loewer*/
00041 /*    3 June, 1999*/
00042 /* */
00043 /* -----------------------------------------------------------------------------*/
00044 
00045 package require Tcl 8.4
00046 package require asn 0.7
00047 package provide ldap 1.6.8
00048 
00049 namespace ldap {
00050 
00051     namespace export    connect secure_connect  \
00052                         disconnect              \
00053                         bind unbind             \
00054                         bindSASL                \
00055                         search                  \
00056                         searchInit              \
00057                 searchNext          \
00058                 searchEnd       \
00059                         modify                  \
00060                         modifyMulti             \
00061                         add                     \
00062                 addMulti        \
00063                         delete                  \
00064                         modifyDN        \
00065                 info
00066 
00067     namespace import ::asn::*
00068     
00069     variable SSLCertifiedAuthoritiesFile
00070     variable doDebug
00071 
00072      doDebug =  0
00073    
00074     /*  LDAP result codes from the RFC*/
00075     variable resultCode2String
00076     array  resultCode2String =  {
00077          0  success
00078          1  operationsError
00079          2  protocolError
00080          3  timeLimitExceeded
00081          4  sizeLimitExceeded
00082          5  compareFalse
00083          6  compareTrue
00084          7  authMethodNotSupported
00085          8  strongAuthRequired
00086         10  referral
00087         11  adminLimitExceeded
00088         12  unavailableCriticalExtension
00089         13  confidentialityRequired
00090         14  saslBindInProgress
00091         16  noSuchAttribute
00092         17  undefinedAttributeType
00093         18  inappropriateMatching
00094         19  constraintViolation
00095         20  attributeOrValueExists
00096         21  invalidAttributeSyntax
00097         32  noSuchObject
00098         33  aliasProblem
00099         34  invalidDNSyntax
00100         35  isLeaf
00101         36  aliasDereferencingProblem
00102         48  inappropriateAuthentication
00103         49  invalidCredentials
00104         50  insufficientAccessRights
00105         51  busy
00106         52  unavailable
00107         53  unwillingToPerform
00108         54  loopDetect
00109         64  namingViolation
00110         65  objectClassViolation
00111         66  notAllowedOnNonLeaf
00112         67  notAllowedOnRDN
00113         68  entryAlreadyExists
00114         69  objectClassModsProhibited
00115         80  other
00116     }
00117     
00118 }
00119 
00120 
00121 /* -----------------------------------------------------------------------------*/
00122 /*     Lookup an numerical ldap result code and return a string version*/
00123 /* */
00124 /* -----------------------------------------------------------------------------*/
00125 ret  ::ldap::resultCode2String (type code) {
00126     variable resultCode2String
00127     if {[::info exists resultCode2String($code)]} {
00128         return $resultCode2String($code)
00129     } else {
00130         return "unknownError"
00131     }
00132 }
00133 
00134 /* -----------------------------------------------------------------------------*/
00135 /*    Basic sanity check for connection handles*/
00136 /*    must be an array*/
00137 /* -----------------------------------------------------------------------------*/
00138 ret  ::ldap::CheckHandle (type handle) {
00139     if {![array exists $handle]} {
00140         return -code error \
00141             [format "Not a valid LDAP connection handle: %s" $handle]
00142     }
00143 }
00144 
00145 /* -----------------------------------------------------------------------------*/
00146 /*     info*/
00147 /* */
00148 /* -----------------------------------------------------------------------------*/
00149 
00150 ret  ldap::info (type args) {
00151    set cmd [lindex $args 0]
00152    set cmds {connections bound bounduser control extensions features ip saslmechanisms tls whoami}
00153    if {[llength $args] == 0} {
00154     return -code error \
00155         "Usage: \"info subcommand ?handle?\""    
00156    }
00157    if {[lsearch -exact $cmds $cmd] == -1} {
00158     return -code error \
00159         "Invalid subcommand \"$cmd\", valid commands are\
00160         [join [lrange $cmds 0 end-1] ,] and [lindex $cmds end]" 
00161    }
00162    eval [linsert [lrange $args 1 end] 0 ldap::info_$cmd]    
00163 }
00164 
00165 /* -----------------------------------------------------------------------------*/
00166 /*     get the ip address of the server we connected to*/
00167 /*  */
00168 /* -----------------------------------------------------------------------------*/
00169 ret  ldap::info_ip (type args) {
00170    if {[llength $args] != 1} {
00171     return -code error \
00172            "Wrong # of arguments. Usage: ldap::info ip handle"
00173    }
00174    CheckHandle [lindex $args 0]
00175    upvar #0 [lindex $args 0] conn
00176    if {![::info exists conn(sock)]} {
00177     return -code error \
00178         "\"[lindex $args 0]\" is not a ldap connection handle"
00179    }
00180    return [lindex [fconfigure $conn(sock) -peername] 0]
00181 }
00182 
00183 /* -----------------------------------------------------------------------------*/
00184 /*    get the list of open ldap connections*/
00185 /* */
00186 /* -----------------------------------------------------------------------------*/
00187 ret  ldap::info_connections (type args) {
00188    if {[llength $args] != 0} {
00189     return -code error \
00190            "Wrong # of arguments. Usage: ldap::info connections"   
00191    }
00192    return [::info vars ::ldap::ldap*]
00193 }
00194 
00195 /* -----------------------------------------------------------------------------*/
00196 /*    check if the connection is bound*/
00197 /* */
00198 /* -----------------------------------------------------------------------------*/
00199 ret  ldap::info_bound (type args) {
00200    if {[llength $args] != 1} {
00201     return -code error \
00202            "Wrong # of arguments. Usage: ldap::info bound handle"
00203    }
00204    CheckHandle [lindex $args 0]
00205    upvar #0 [lindex $args 0] conn
00206    if {![::info exists conn(bound)]} {
00207     return -code error \
00208         "\"[lindex $args 0]\" is not a ldap connection handle"
00209    }
00210    
00211    return $conn(bound)
00212 }
00213 
00214 /* -----------------------------------------------------------------------------*/
00215 /*    check with which user the connection is bound*/
00216 /* */
00217 /* -----------------------------------------------------------------------------*/
00218 ret  ldap::info_bounduser (type args) {
00219    if {[llength $args] != 1} {
00220     return -code error \
00221            "Wrong # of arguments. Usage: ldap::info bounduser handle"
00222    }
00223    CheckHandle [lindex $args 0]   
00224    upvar #0 [lindex $args 0] conn
00225    if {![::info exists conn(bound)]} {
00226     return -code error \
00227         "\"[lindex $args 0]\" is not a ldap connection handle"
00228    }
00229    
00230    return $conn(bounduser)
00231 }
00232 
00233 /* -----------------------------------------------------------------------------*/
00234 /*    check if the connection uses tls*/
00235 /* */
00236 /* -----------------------------------------------------------------------------*/
00237 
00238 ret  ldap::info_tls (type args) {
00239    if {[llength $args] != 1} {
00240     return -code error \
00241            "Wrong # of arguments. Usage: ldap::info tls handle"
00242    }
00243    CheckHandle [lindex $args 0]   
00244    upvar #0 [lindex $args 0] conn
00245    if {![::info exists conn(tls)]} {
00246     return -code error \
00247         "\"[lindex $args 0]\" is not a ldap connection handle"
00248    }
00249    return $conn(tls)
00250 }
00251 
00252 ret  ldap::info_saslmechanisms (type args) {
00253    if {[llength $args] != 1} {
00254     return -code error \
00255            "Wrong # of arguments. Usage: ldap::info saslmechanisms handle"
00256    }
00257    return [Saslmechanisms [lindex $args 0]]
00258 }
00259 
00260 ret  ldap::info_extensions (type args) {
00261    if {[llength $args] != 1} {
00262     return -code error \
00263            "Wrong # of arguments. Usage: ldap::info extensions handle"
00264    }
00265    return [Extensions [lindex $args 0]]
00266 }
00267 
00268 ret  ldap::info_control (type args) {
00269    if {[llength $args] != 1} {
00270     return -code error \
00271            "Wrong # of arguments. Usage: ldap::info control handle"
00272    }
00273    return [Control [lindex $args 0]]
00274 }
00275 
00276 ret  ldap::info_features (type args) {
00277    if {[llength $args] != 1} {
00278     return -code error \
00279            "Wrong # of arguments. Usage: ldap::info features handle"
00280    }
00281    return [Features [lindex $args 0]]
00282 }
00283 
00284 ret  ldap::info_whoami (type args) {
00285    if {[llength $args] != 1} {
00286     return -code error \
00287            "Wrong # of arguments. Usage: ldap::info whoami handle"
00288    }
00289    return [Whoami [lindex $args 0]]
00290 }
00291 
00292 
00293 /* -----------------------------------------------------------------------------*/
00294 /*  Basic server introspection support*/
00295 /* */
00296 /* -----------------------------------------------------------------------------*/
00297 ret  ldap::Saslmechanisms (type conn) {
00298     CheckHandle $conn
00299     lindex [ldap::search $conn {} {(objectClass=*)} \
00300                     {supportedSASLMechanisms} -scope base] 0 1 1
00301 }
00302 
00303 ret  ldap::Extensions (type conn) {
00304     CheckHandle $conn
00305     lindex [ldap::search $conn {} {(objectClass=*)} \
00306                     {supportedExtension} -scope base] 0 1 1
00307 }
00308 
00309 ret  ldap::Control (type conn) {
00310     CheckHandle $conn
00311     lindex [ldap::search $conn {} {(objectClass=*)} \
00312                     {supportedControl} -scope base] 0 1 1
00313 }
00314 
00315 ret  ldap::Features (type conn) {
00316     CheckHandle $conn
00317     lindex [ldap::search $conn {} {(objectClass=*)} \
00318                     {supportedFeatures} -scope base] 0 1 1
00319 }
00320 
00321 /* -------------------------------------------------------------------------------*/
00322 /*  Implements the RFC 4532 extension "Who am I?"*/
00323 /* */
00324 /* -------------------------------------------------------------------------------*/
00325 ret  ldap::Whoami (type handle) {
00326     CheckHandle $handle
00327     if {[lsearch [ldap::Extensions $handle] 1.3.6.1.4.1.4203.1.11.3] == -1} {
00328         return -code error \
00329             "Server does not support the \"Who am I?\" extension"
00330     }
00331     
00332     set request [asnApplicationConstr 23 [asnOctetString 1.3.6.1.4.1.4203.1.11.3]]
00333     set mid [SendMessage $handle $request]
00334     set response [WaitForResponse $handle $mid]
00335  
00336     asnGetApplication response appNum
00337     if {$appNum != 24} {
00338         return -code error \
00339              "unexpected application number ($appNum != 24)"        
00340     }
00341     
00342     asnGetEnumeration response resultCode
00343     asnGetOctetString response matchedDN
00344     asnGetOctetString response errorMessage
00345     if {$resultCode != 0} {
00346         return -code error \
00347         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
00348         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
00349     }
00350     set whoami ""
00351     if {[string length $response]} {
00352         asnRetag response 0x04
00353         asnGetOctetString response whoami
00354     }
00355     return $whoami
00356 }
00357 
00358 /* -----------------------------------------------------------------------------*/
00359 /*     connect*/
00360 /* */
00361 /* -----------------------------------------------------------------------------*/
00362 ret  ldap::connect ( type host , optional port =389 ) {
00363 
00364     #--------------------------------------
00365     #   connect via TCP/IP
00366     #--------------------------------------
00367     set sock [socket $host $port]
00368     fconfigure $sock -blocking no -translation binary -buffering full
00369 
00370     #--------------------------------------
00371     #   initialize connection array
00372     #--------------------------------------
00373     upvar #0 ::ldap::ldap$sock conn
00374     catch { unset conn }
00375 
00376     set conn(host)      $host
00377     set conn(sock)      $sock
00378     set conn(messageId) 0
00379     set conn(tls)       0
00380     set conn(bound)     0
00381     set conn(bounduser) ""
00382     set conn(saslBindInProgress) 0
00383     set conn(tlsHandshakeInProgress) 0
00384     set conn(lastError) ""
00385     
00386     fileevent $sock readable [list ::ldap::MessageReceiver ::ldap::ldap$sock]
00387     return ::ldap::ldap$sock
00388 }
00389 
00390 /* -----------------------------------------------------------------------------*/
00391 /*     secure_connect*/
00392 /* */
00393 /* -----------------------------------------------------------------------------*/
00394 ret  ldap::secure_connect ( type host , optional port =636 ) {
00395 
00396     variable SSLCertifiedAuthoritiesFile
00397 
00398     package require tls
00399 
00400     #------------------------------------------------------------------
00401     #   connect via TCP/IP
00402     #------------------------------------------------------------------
00403     set sock [socket $host $port]
00404     fconfigure $sock -blocking no -translation binary -buffering full
00405 
00406     #------------------------------------------------------------------
00407     #   make it a SSL connection
00408     #
00409     #------------------------------------------------------------------
00410     #tls::import $sock -cafile $SSLCertifiedAuthoritiesFile -ssl2 no -ssl3 yes -tls1 yes
00411     tls::import $sock -cafile "" -certfile "" -keyfile "" \
00412                       -request 1 -server 0 -require 0 -ssl2 no -ssl3 yes -tls1 yes
00413     set retry 0
00414     while {1} {
00415         if {$retry > 20} {
00416             close $sock
00417             return -code error "too long retry to setup SSL connection"
00418         }
00419         if {[catch { tls::handshake $sock } err]} {
00420             if {[string match "*resource temporarily unavailable*" $err]} {
00421                 after 50
00422                 incr retry
00423             } else {
00424                 close $sock
00425                 return -code error $err
00426             }
00427         } else {
00428             break
00429         }
00430     }
00431 
00432     #--------------------------------------
00433     #   initialize connection array
00434     #--------------------------------------
00435     upvar ::ldap::ldap$sock conn
00436     catch { unset conn }
00437 
00438     set conn(host)      $host
00439     set conn(sock)      $sock
00440     set conn(messageId) 0
00441     set conn(tls)       1
00442     set conn(bound)     0
00443     set conn(bounduser) ""
00444     set conn(saslBindInProgress) 0
00445     set conn(tlsHandshakeInProgress) 0
00446     set conn(lasterror) ""
00447     
00448     fileevent $sock readable [list ::ldap::MessageReceiver ::ldap::ldap$sock]
00449     return ::ldap::ldap$sock
00450 }
00451 
00452 
00453 /* ------------------------------------------------------------------------------*/
00454 /*     starttls -  negotiate tls on an open ldap connection*/
00455 /* */
00456 /* ------------------------------------------------------------------------------*/
00457 ret  ldap::starttls (type handle , optional cafile ="" , optional certfile ="" , optional keyfile ="") {
00458     CheckHandle $handle
00459 
00460     upvar #0 $handle conn
00461     
00462     if {$conn(tls)} {
00463         return -code error \
00464             "Cannot StartTLS on connection, TLS already running"
00465     }
00466     
00467     if {[ldap::waitingForMessages $handle]} {
00468         return -code error \
00469             "Cannot StartTLS while waiting for repsonses"
00470     }
00471     
00472     if {$conn(saslBindInProgress)} {
00473         return -code error \
00474             "Cannot StartTLS while SASL bind in progress"
00475     }
00476     
00477     if {[lsearch -exact [ldap::Extensions $handle] 1.3.6.1.4.1.1466.20037] == -1} {
00478         return -code error \
00479             "Server does not support the StartTLS extension"
00480     }
00481     package require tls
00482     
00483     
00484     set request [asnApplicationConstr 23 [asnOctetString 1.3.6.1.4.1.1466.20037]]
00485     set mid [SendMessage $handle $request]
00486     set conn(tlsHandshakeInProgress) 1
00487     set response [WaitForResponse $handle $mid]
00488  
00489     asnGetApplication response appNum
00490     if {$appNum != 24} {
00491         set conn(tlsHandshakeInProgress) 0
00492         return -code error \
00493              "unexpected application number ($appNum != 24)"        
00494     }
00495     
00496     asnGetEnumeration response resultCode
00497     asnGetOctetString response matchedDN
00498     asnGetOctetString response errorMessage
00499     if {$resultCode != 0} {
00500         set conn(tlsHandshakeInProgress) 0
00501         return -code error \
00502         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
00503         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
00504     }
00505     set oid "1.3.6.1.4.1.1466.20037"
00506     if {[string length $response]} {
00507         asnRetag response 0x04
00508         asnGetOctetString response oid
00509     }
00510     if {$oid ne "1.3.6.1.4.1.1466.20037"} {
00511         set conn(tlsHandshakeInProgress) 0
00512         return -code error \
00513             "Unexpected LDAP response"
00514     } 
00515 
00516     tls::import $conn(sock) -cafile $cafile -certfile $certfile -keyfile $keyfile \
00517                       -request 1 -server 0 -require 0 -ssl2 no -ssl3 yes -tls1 yes
00518     set retry 0
00519     while {1} {
00520         if {$retry > 20} {
00521             close $sock
00522             return -code error "too long retry to setup SSL connection"
00523         }
00524         if {[catch { tls::handshake $conn(sock) } err]} {
00525             if {[string match "*resource temporarily unavailable*" $err]} {
00526                 after 50
00527                 incr retry
00528             } else {
00529                 close $conn(sock)
00530                 return -code error $err
00531             }
00532         } else {
00533             break
00534         }
00535     }
00536     set conn(tls) 1
00537     set conn(tlsHandshakeInProgress) 0
00538     return 1
00539 }
00540 
00541 
00542 
00543 /* ------------------------------------------------------------------------------*/
00544 /*   Create a new unique message and send it over the socket.*/
00545 /* */
00546 /* ------------------------------------------------------------------------------*/
00547 
00548 ret  ldap::CreateAndSendMessage (type handle , type payload) {
00549     upvar #0 $handle conn
00550     
00551     if {$conn(tlsHandshakeInProgress)} {
00552         return -code error \
00553             "Cannot send other LDAP PDU while TLS handshake in progress"
00554     }
00555     
00556     incr conn(messageId)
00557     set message [asnSequence [asnInteger $conn(messageId)] $payload]
00558     debugData "Message $conn(messageId) Sent" $message
00559     puts -nonewline $conn(sock) $message
00560     flush $conn(sock)
00561     return $conn(messageId)
00562 }
00563 
00564 /* ------------------------------------------------------------------------------*/
00565 /*   Send a message to the server which expects a response,*/
00566 /*   returns the messageId which is to be used with FinalizeMessage */
00567 /*   and WaitForResponse*/
00568 /* */
00569 /* ------------------------------------------------------------------------------*/
00570 ret  ldap::SendMessage (type handle , type pdu) {
00571     upvar #0 $handle conn
00572     set mid [CreateAndSendMessage $handle $pdu] 
00573     
00574     # safe the state to match responses   
00575     set conn(message,$mid) [list]
00576     return $mid                
00577 }
00578 
00579 /* ------------------------------------------------------------------------------*/
00580 /*   Send a message to the server without expecting a response*/
00581 /* */
00582 /* ------------------------------------------------------------------------------*/
00583 ret  ldap::SendMessageNoReply (type handle , type pdu) {
00584     upvar #0 $handle conn
00585     return [CreateAndSendMessage $handle $pdu]                
00586 }
00587 
00588 /* ------------------------------------------------------------------------------*/
00589 /*  Cleanup the storage associated with a messageId*/
00590 /* */
00591 /* ------------------------------------------------------------------------------*/
00592 ret  ldap::FinalizeMessage (type handle , type messageId) {
00593     upvar #0 $handle conn
00594     trace "Message $messageId finalized"
00595     unset -nocomplain conn(message,$messageId)
00596 }
00597 
00598 /* ------------------------------------------------------------------------------*/
00599 /*   Wait for a response for the given messageId.*/
00600 /* */
00601 /*   This waits in a vwait if no message has yet been received or returns*/
00602 /*   the oldest message at once, if it is queued.*/
00603 /* */
00604 /* ------------------------------------------------------------------------------*/
00605 ret  ldap::WaitForResponse (type handle , type messageId) {
00606     upvar #0 $handle conn
00607     
00608     trace "Waiting for Message $messageId"
00609     # check if the message waits for a reply
00610     if {![::info exists conn(message,$messageId)]} {
00611         return -code error \
00612             [format "Cannot wait for message %d." $messageId]
00613     }
00614     
00615     # check if we have a received response in the buffer
00616     if {[llength $conn(message,$messageId)] > 0} {
00617         set response [lindex $conn(message,$messageId) 0]
00618         set conn(message,$messageId) [lrange $conn(message,$messageId) 1 end]
00619         return $response
00620     }
00621     
00622     # wait for an incoming response
00623     vwait [namespace which -variable $handle](message,$messageId)
00624     if {[llength $conn(message,$messageId)] == 0} {
00625         # We have waited and have been awakended but no message is there
00626         if {[string length $conn(lastError)]} {
00627             return -code error \
00628                 [format "Protocol error: %s" $conn(lastError)]
00629         } else {
00630             return -code error \
00631                 [format "Broken response for message %d" $messageId]
00632         }
00633     }
00634     set response [lindex $conn(message,$messageId) 0]
00635     set conn(message,$messageId) [lrange $conn(message,$messageId) 1 end]
00636     return $response        
00637 }
00638 
00639 ret  ldap::waitingForMessages (type handle) {
00640     upvar #0 $handle conn
00641     return [llength [array names conn message,*]]
00642 }
00643 
00644 /* ------------------------------------------------------------------------------*/
00645 /*  Process a single response PDU. Decodes the messageId and puts the*/
00646 /*  message into the appropriate queue.*/
00647 /* */
00648 /* ------------------------------------------------------------------------------*/
00649 
00650 ret  ldap::ProcessMessage (type handle , type response) {
00651     upvar #0 $handle conn
00652 
00653     # decode the messageId
00654     asnGetInteger  response messageId
00655     
00656     # check if we wait for a response
00657     if {[::info exists conn(message,$messageId)]} {
00658         # append the new message, which triggers 
00659         # message handlers using vwait on the entry
00660         lappend conn(message,$messageId) $response
00661         return
00662     }
00663     
00664     # handle unsolicited server responses
00665     
00666     if {0} {
00667         asnGetApplication response appNum
00668         #if { $appNum != 24 } {
00669         #     error "unexpected application number ($appNum != 24)"
00670         #}
00671         asnGetEnumeration response resultCode
00672         asnGetOctetString response matchedDN
00673         asnGetOctetString response errorMessage
00674         if {[string length $response]} {
00675             asnGetOctetString response responseName
00676         }
00677         if {[string length $response]} {
00678             asnGetOctetString response responseValue
00679         }
00680         if {$resultCode != 0} {
00681             return -code error \
00682             -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
00683             "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"    
00684         }
00685     }
00686     #dumpASN1Parse $response
00687     #error "Unsolicited message from server"
00688     
00689 }
00690 
00691 /* -------------------------------------------------------------------------------*/
00692 /*  Get the code out of waitForResponse in case of errors*/
00693 /* */
00694 /* -------------------------------------------------------------------------------*/
00695 ret  ldap::CleanupWaitingMessages (type handle) {
00696     upvar #0 $handle conn
00697     foreach message [array names conn message,*] {
00698         set conn($message) [list]
00699     }
00700 }
00701 
00702 /* -------------------------------------------------------------------------------*/
00703 /*   The basic fileevent based message receiver.*/
00704 /*   It reads PDU's from the network in a non-blocking fashion.*/
00705 /* */
00706 /* -------------------------------------------------------------------------------*/
00707 ret  ldap::MessageReceiver (type handle) {
00708     upvar #0 $handle conn
00709     
00710     # We have to account for partial PDUs received, so
00711     # we keep some state information.
00712     #
00713     #   conn(pdu,partial)  -- we are reading a partial pdu if non zero
00714     #   conn(pdu,length_bytes) -- the buffer for loading the length
00715     #   conn(pdu,length)   -- we have decoded the length if >= 0, if <0 it contains 
00716     #                         the length of the length encoding in bytes
00717     #   conn(pdu,payload)  -- the payload buffer
00718     #   conn(pdu,received) -- the data received
00719     
00720     # fetch the sequence byte
00721     if {[::info exists conn(pdu,partial)] && $conn(pdu,partial) != 0} {
00722         # we have decoded at least the type byte    
00723     } else {
00724         foreach {code type} [ReceiveBytes $conn(sock) 1] {break}
00725         switch -- $code {
00726             ok {
00727                 binary scan $type c byte
00728                 set type [expr {($byte + 0x100) % 0x100}]  
00729                 if {$type != 0x30} {
00730                     CleanupWaitingMessages $handle
00731                     set conn(lastError) [format "Expected SEQUENCE (0x30) but got %x" $type]
00732                     return
00733                 } else {
00734                     set conn(pdu,partial) 1
00735                     append conn(pdu,received) $type
00736                 }
00737                 }
00738             eof {
00739                 CleanupWaitingMessages $handle
00740                 set conn(lastError) "Server closed connection"
00741                 catch {close $conn(sock)}
00742                 return
00743             } 
00744             default {
00745                 CleanupWaitingMessages $handle
00746                 set bytes $type[read $conn(sock)]
00747                 binary scan $bytes h* values
00748                 set conn(lastError) [format \
00749                     "Error reading SEQUENCE response for handle %s : %s : %s" $handle $code $values]
00750                 return
00751                 }
00752         }
00753     }
00754     
00755     
00756     # fetch the length
00757     if {[::info exists conn(pdu,length)] && $conn(pdu,length) >= 0} {
00758         # we already have a decoded length
00759     } else {
00760         if {[::info exists conn(pdu,length)] && $conn(pdu,length) < 0} {
00761             # we already know the length, but have not received enough bytes to decode it
00762             set missing [expr {1+abs($conn(pdu,length))-[string length $conn(pdu,length_bytes)]}]
00763             if {$missing != 0} {
00764                 foreach {code bytes} [ReceiveBytes $conn(sock) $missing] {break}
00765                 switch -- $code {
00766                     "ok"  {
00767                         append conn(pdu,length_bytes) $bytes
00768                         append conn(pdu,received) $bytes
00769                         asnGetLength conn(pdu,length_bytes) conn(pdu,length)
00770                     }
00771                     "partial" {
00772                         append conn(pdu,length_bytes) $bytes
00773                         append conn(pdu,received) $bytes
00774                         return
00775                     }
00776                     "eof" {
00777                         CleanupWaitingMessages $handle            
00778                         catch {close $conn(sock)}
00779                         set conn(lastError) "Server closed connection"
00780                         return
00781                     } 
00782                     default {
00783                         CleanupWaitingMessages $handle            
00784                         set conn(lastError) [format \
00785                             "Error reading LENGTH2 response for handle %s : %s" $handle $code]
00786                         return
00787                     }
00788                 }
00789             }
00790         } else {
00791             # we know nothing, need to read the first length byte
00792             foreach {code bytes} [ReceiveBytes $conn(sock) 1] {break}
00793             switch -- $code {
00794                 "ok"  {
00795                     set conn(pdu,length_bytes) $bytes
00796                     binary scan $bytes c byte
00797                     set size [expr {($byte + 0x100) % 0x100}]  
00798                     if {$size > 0x080} {
00799                         set conn(pdu,length) [expr {-1* ($size & 0x7f)}]
00800                         # fetch the rest with the next fileevent
00801                         return 
00802                     } else {
00803                         asnGetLength conn(pdu,length_bytes) conn(pdu,length)
00804                     }
00805                 }
00806                 "eof" {
00807                     CleanupWaitingMessages $handle            
00808                     catch {close $conn(sock)}
00809                     set conn(lastError) "Server closed connection"
00810                 }                 
00811                 default {
00812                     CleanupWaitingMessages $handle            
00813                     set conn(lastError) [format \
00814                         "Error reading LENGTH1 response for handle %s : %s" $handle $code]
00815                     return
00816                 }       
00817             }
00818         }
00819     }
00820     
00821     if {[::info exists conn(pdu,payload)]} {
00822         # length is decoded, we can read the rest
00823         set missing [expr {$conn(pdu,length) - [string length $conn(pdu,payload)]}]
00824     } else {
00825         set missing $conn(pdu,length)
00826     }
00827     if {$missing > 0} {
00828         foreach {code bytes} [ReceiveBytes $conn(sock) $missing] {break}
00829         switch -- $code {
00830             "ok" {
00831                 append conn(pdu,payload) $bytes
00832             }
00833             "partial" {
00834                 append conn(pdu,payload) $bytes
00835                 return
00836             }
00837             "eof" {
00838                 CleanupWaitingMessages $handle            
00839                 catch {close $conn(sock)}
00840                 set conn(lastError) "Server closed connection"
00841             }             
00842             default {
00843                 CleanupWaitingMessages $handle            
00844                 set conn(lastError) [format \
00845                     "Error reading DATA response for handle %s : %s" $handle $code]
00846                 return
00847             }
00848         }
00849     }
00850     
00851     # we have a complete PDU, push it for processing
00852     set pdu $conn(pdu,payload)
00853     set conn(pdu,payload) ""
00854     set conn(pdu,partial) 0
00855     unset -nocomplain set conn(pdu,length) 
00856     set conn(pdu,length_bytes) ""    
00857    
00858     # reschedule message Processing
00859     after 0 [list ::ldap::ProcessMessage $handle $pdu]
00860 }
00861 
00862 /* -------------------------------------------------------------------------------*/
00863 /*  Receive the number of bytes from the socket and signal error conditions.*/
00864 /* */
00865 /* -------------------------------------------------------------------------------*/
00866 ret  ldap::ReceiveBytes (type sock , type bytes) {
00867     set status [catch {read $sock $bytes} block]
00868     if { $status != 0 } {
00869         return [list error $block]
00870     } elseif { [string length $block] == $bytes } {
00871         # we have all bytes we wanted
00872         return [list ok $block]
00873     } elseif { [eof $sock] } {
00874         return [list eof $block]
00875     } elseif { [fblocked $sock] || ([string length $block] < $bytes)} {
00876         return [list partial $block]
00877     } else {
00878         error "Socket state for socket $sock undefined!" 
00879     }  
00880 }
00881 
00882 /* -----------------------------------------------------------------------------*/
00883 /*     bindSASL  -  does a bind with SASL authentication*/
00884 /* -----------------------------------------------------------------------------*/
00885 
00886 ret  ldap::bindSASL (type handle , optional name ="" , optional password ="" ) {
00887     CheckHandle $handle
00888 
00889     package require SASL
00890     
00891     upvar #0 $handle conn
00892     
00893     set mechs [ldap::Saslmechanisms $handle]
00894     
00895     set conn(saslBindInProgress) 1    
00896     set auth 0
00897     foreach mech [SASL::mechanisms] {
00898         if {[lsearch -exact $mechs $mech] == -1} { continue }
00899         trace "Using $mech for SASL Auth"
00900         if {[catch {
00901             SASLAuth $handle $mech $name $password
00902         } msg]} {
00903             trace [format "AUTH %s failed: %s" $mech $msg]
00904         } else {
00905        # AUTH was successful 
00906        if {$msg == 1} {
00907            set auth 1
00908            break
00909        }
00910     }    
00911     }        
00912     
00913     set conn(saslBindInProgress) 0
00914     return $auth
00915 }
00916 
00917 /* -----------------------------------------------------------------------------*/
00918 /*     SASLCallback - Callback to use for SASL authentication*/
00919 /* */
00920 /*     More or less cut and copied from the smtp module.*/
00921 /*     May need adjustments for ldap.*/
00922 /* */
00923 /* -----------------------------------------------------------------------------*/
00924 ret  ::ldap::SASLCallback (type handle , type context , type command , type args) {
00925     upvar #0 $handle conn
00926     upvar #0 $context ctx
00927     array set options $conn(options)
00928     trace "SASLCallback $command"
00929     switch -exact -- $command {
00930         login    { return $options(-username) }
00931         username { return $options(-username) }
00932         password { return $options(-password) }
00933         hostname { return [::info hostname] }
00934         realm    { 
00935             if {[string equal $ctx(mech) "NTLM"] \
00936                     && [info exists ::env(USERDOMAIN)]} {
00937                 return $::env(USERDOMAIN)
00938             } else {
00939                 return ""
00940             }
00941         }
00942         default  { 
00943             return -code error "error: unsupported SASL information requested"
00944         }
00945     }
00946 }
00947 
00948 /* -----------------------------------------------------------------------------*/
00949 /*     SASLAuth - Handles the actual SASL message exchange*/
00950 /* */
00951 /* -----------------------------------------------------------------------------*/
00952 
00953 ret  ldap::SASLAuth (type handle , type mech , type name , type password) {
00954     upvar 1 $handle conn
00955     
00956     set conn(options) [list -password $password -username $name]
00957 
00958     # check for tcllib bug # 1545306 and reset the nonce-count if 
00959     # found, so a second call to this code does not fail
00960     #
00961     if {[::info exists ::SASL::digest_md5_noncecount]} {
00962         set ::SASL::digest_md5_noncecount 0
00963     }
00964     
00965     set ctx [SASL::new -mechanism $mech \
00966                        -service ldap    \
00967                        -callback [list ::ldap::SASLCallback $handle]]
00968 
00969     set msg(serverSASLCreds) ""
00970     # Do the SASL Message exchanges
00971     while {[SASL::step $ctx $msg(serverSASLCreds)]} {
00972         # Create and send the BindRequest
00973         set request [buildSASLBindRequest "" $mech [SASL::response $ctx]]
00974         set messageId [SendMessage $handle $request]
00975         debugData bindRequest $request
00976         
00977         set response [WaitForResponse $handle $messageId]
00978         FinalizeMessage $handle $messageId
00979         debugData bindResponse $response
00980         
00981         array set msg [decodeSASLBindResponse $handle $response]
00982         
00983     # Check for Bind success
00984         if {$msg(resultCode) == 0} { 
00985             set conn(bound) 1
00986             set conn(bounduser) $name
00987             SASL::cleanup $ctx
00988             break        
00989         }
00990         
00991     # Check if next SASL step is requested
00992         if {$msg(resultCode) == 14} {
00993             continue
00994         }
00995     
00996         SASL::cleanup $ctx
00997         # Something went wrong
00998         return  -code error \
00999         -errorcode [list LDAP [resultCode2String $msg(resultCode)] \
01000                  $msg(matchedDN) $msg(errorMessage)] \
01001         "LDAP error [resultCode2String $msg(resultCode)] '$msg(matchedDN)': $msg(errorMessage)"
01002     }
01003     
01004     return 1
01005 }
01006 
01007 /* ----------------------------------------------------------------------------*/
01008 /* */
01009 /*  Create a LDAP BindRequest using SASL*/
01010 /* */
01011 /* ----------------------------------------------------------------------------*/
01012 
01013 ret  ldap::buildSASLBindRequest (type name , type mech , optional credentials ={)} {
01014     if {$credentials ne {}} {
01015         request =  [  asnApplicationConstr 0                    \
01016             [asnInteger 3]                      \
01017             [asnOctetString $name]              \
01018             [asnChoiceConstr 3                      \
01019                     [asnOctetString $mech]          \
01020                     [asnOctetString $credentials]   \
01021             ]  \                                                                          
01022         ] 
01023     } else {  
01024      request =  [   asnApplicationConstr 0                  \
01025         [asnInteger 3]                      \
01026         [asnOctetString $name]              \
01027         [asnChoiceConstr 3                      \
01028                 [asnOctetString $mech]          \
01029         ] \
01030         ]                                                                         
01031     }
01032     return $request
01033 }
01034 
01035 /* -------------------------------------------------------------------------------*/
01036 /* */
01037 /*  Decode an LDAP BindResponse*/
01038 /* */
01039 /* -------------------------------------------------------------------------------*/
01040 ret  ldap::decodeSASLBindResponse (type handle , type response) {
01041     upvar #0 $handle conn
01042 
01043     asnGetApplication response appNum
01044     if { $appNum != 1 } {
01045         error "unexpected application number ($appNum != 1)"
01046     }
01047     asnGetEnumeration response resultCode
01048     asnGetOctetString response matchedDN
01049     asnGetOctetString response errorMessage
01050 
01051     # Check if we have a serverSASLCreds field left,
01052     # or if this is a simple response without it
01053     # probably an error message then.
01054     if {[string length $response]} {
01055         asnRetag response 0x04
01056         asnGetOctetString response serverSASLCreds
01057     } else {
01058         set serverSASLCreds ""
01059     } 
01060     return [list appNum $appNum \
01061                  resultCode $resultCode matchedDN $matchedDN \
01062                  errorMessage $errorMessage serverSASLCreds $serverSASLCreds]
01063 }
01064 
01065 
01066 /* -----------------------------------------------------------------------------*/
01067 /*     bind  -  does a bind with simple authentication*/
01068 /* */
01069 /* -----------------------------------------------------------------------------*/
01070 ret  ldap::bind ( type handle , optional name ="" , optional password ="" ) {
01071     CheckHandle $handle
01072     
01073     upvar #0 $handle conn
01074 
01075     #-----------------------------------------------------------------
01076     #   marshal bind request packet and send it
01077     #
01078     #-----------------------------------------------------------------
01079     set request [asnApplicationConstr 0                \
01080                         [asnInteger 3]                 \
01081                         [asnOctetString $name]         \
01082                         [asnChoice 0 $password]        \
01083                 ]                                  
01084     set messageId [SendMessage $handle $request]            
01085     debugData bindRequest $request
01086     
01087     set response [WaitForResponse $handle $messageId]
01088     FinalizeMessage $handle $messageId
01089     debugData bindResponse $response
01090     
01091     asnGetApplication response appNum
01092     if { $appNum != 1 } {
01093         error "unexpected application number ($appNum != 1)"
01094     }
01095     asnGetEnumeration response resultCode
01096     asnGetOctetString response matchedDN
01097     asnGetOctetString response errorMessage
01098     if {$resultCode != 0} {
01099         return -code error \
01100         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01101         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
01102     }
01103     set conn(bound) 1
01104     set conn(bounduser) $name
01105 }
01106 
01107 
01108 /* -----------------------------------------------------------------------------*/
01109 /*     unbind*/
01110 /* */
01111 /* -----------------------------------------------------------------------------*/
01112 ret  ldap::unbind ( type handle ) {
01113     CheckHandle $handle
01114 
01115     upvar #0 $handle conn
01116 
01117     #------------------------------------------------
01118     #   marshal unbind request packet and send it
01119     #------------------------------------------------
01120     set request [asnApplication 2 ""]         
01121     SendMessageNoReply $handle $request
01122     
01123     set conn(bounduser) ""
01124     set conn(bound) 0
01125     close $conn(sock)
01126     set conn(sock) ""
01127 }
01128 
01129 
01130 /* -----------------------------------------------------------------------------*/
01131 /*     buildUpFilter  -   parses the text representation of LDAP search*/
01132 /*                        filters and transforms it into the correct*/
01133 /*                        marshalled representation for the search request*/
01134 /*                        packet*/
01135 /* */
01136 /* -----------------------------------------------------------------------------*/
01137 ret  ldap::buildUpFilter ( type filter ) {
01138 
01139     set first [lindex $filter 0]
01140     set data ""
01141     switch -regexp -- $first {
01142         ^\\&$ {  #--- and -------------------------------------------
01143             foreach term [lrange $filter 1 end] {
01144                 append data [buildUpFilter $term]
01145             }
01146             return [asnChoiceConstr 0 $data]
01147         }
01148         ^\\|$ {  #--- or --------------------------------------------
01149             foreach term [lrange $filter 1 end] {
01150                 append data [buildUpFilter $term]
01151             }
01152             return [asnChoiceConstr 1 $data]
01153         }
01154         ^\\!$ {  #--- not -------------------------------------------
01155             return [asnChoiceConstr 2 [buildUpFilter [lindex $filter 1]]]
01156         }
01157         =\\*$ {  #--- present ---------------------------------------
01158             set endpos [expr {[string length $first] -3}]
01159             set attributetype [string range $first 0 $endpos]
01160             return [asnChoice 7 $attributetype]
01161         }
01162         ^[0-9A-z.]*~= {  #--- approxMatch --------------------------
01163             regexp {^([0-9A-z.]*)~=(.*)$} $first all attributetype value
01164             return [asnChoiceConstr 8 [asnOctetString $attributetype] \
01165                                       [asnOctetString $value]         ]
01166         }
01167         ^[0-9A-z.]*<= {  #--- lessOrEqual --------------------------
01168             regexp {^([0-9A-z.]*)<=(.*)$} $first all attributetype value
01169             return [asnChoiceConstr 6 [asnOctetString $attributetype] \
01170                                       [asnOctetString $value]         ]
01171         }
01172         ^[0-9A-z.]*>= {  #--- greaterOrEqual -----------------------
01173             regexp {^([0-9A-z.]*)>=(.*)$} $first all attributetype value
01174             return [asnChoiceConstr 5 [asnOctetString $attributetype] \
01175                                       [asnOctetString $value]         ]
01176         }
01177         ^[0-9A-z.]*=.*\\*.* {  #--- substrings -----------------
01178             regexp {^([0-9A-z.]*)=(.*)$} $first all attributetype value
01179             regsub -all {\*+} $value {*} value
01180             set value [split $value "*"]
01181             
01182             set firstsubstrtype 0       ;# initial
01183             set lastsubstrtype  2       ;# final
01184             if {[string equal [lindex $value 0] ""]} {
01185                 set firstsubstrtype 1       ;# any
01186                 set value [lreplace $value 0 0]
01187             }
01188             if {[string equal [lindex $value end] ""]} {
01189                 set lastsubstrtype 1        ;# any
01190                 set value [lreplace $value end end]
01191             }
01192         
01193             set n [llength $value]
01194         
01195             set i 1
01196             set l {}
01197             set substrtype 0            ;# initial
01198             foreach str $value {
01199             if {$i == 1 && $i == $n} {
01200                 if {$firstsubstrtype == 0} {
01201                 set substrtype 0    ;# initial
01202                 } elseif {$lastsubstrtype == 2} {
01203                 set substrtype 2    ;# final
01204                 } else {
01205                 set substrtype 1    ;# any
01206                 }
01207             } elseif {$i == 1} {
01208                 set substrtype $firstsubstrtype
01209             } elseif {$i == $n} {
01210                 set substrtype $lastsubstrtype
01211             } else {
01212                 set substrtype 1        ;# any
01213             }
01214             lappend l [asnChoice $substrtype $str]
01215             incr i
01216             }
01217             return [asnChoiceConstr 4 [asnOctetString $attributetype]     \
01218                       [asnSequenceFromList $l] ]
01219         }
01220         ^[0-9A-z.]*= {  #--- equal ---------------------------------
01221             regexp {^([0-9A-z.]*)=(.*)$} $first all attributetype value
01222             trace "equal: attributetype='$attributetype' value='$value'"
01223             return [asnChoiceConstr 3 [asnOctetString $attributetype] \
01224                                       [asnOctetString $value]         ]
01225         }
01226         default {
01227             return [buildUpFilter $first]
01228             #error "cant handle $first for filter part"
01229         }
01230     }
01231 }
01232 
01233 /* -----------------------------------------------------------------------------*/
01234 /*     search  -  performs a LDAP search below the baseObject tree using a*/
01235 /*                complex LDAP search expression (like "|(cn=Linus*)(sn=Torvalds*)"*/
01236 /*                and returns all matching objects (DNs) with given attributes*/
01237 /*                (or all attributes if empty list is given) as list:*/
01238 /* */
01239 /*   {dn1 { attr1 {val11 val12 ...} attr2 {val21 val22 ... } ... }} {dn2 { ... }} ...*/
01240 /* */
01241 /* -----------------------------------------------------------------------------*/
01242 ret  ldap::search ( type handle , type baseObject , type filterString , type attributes , type args) {
01243     CheckHandle $handle
01244 
01245     upvar #0 $handle conn
01246 
01247     searchInit $handle $baseObject $filterString $attributes $args
01248 
01249     set results    {}
01250     set lastPacket 0
01251     while { !$lastPacket } {
01252 
01253     set r [searchNext $handle]
01254     if {[llength $r] > 0} then {
01255         lappend results $r
01256     } else {
01257         set lastPacket 1
01258     }
01259     }
01260     searchEnd $handle
01261 
01262     return $results
01263 }
01264 /* -----------------------------------------------------------------------------*/
01265 /*     searchInProgress - checks if a search is in progress*/
01266 /* */
01267 /* -----------------------------------------------------------------------------*/
01268 
01269 ret  ldap::searchInProgress (type handle) {
01270    CheckHandle $handle
01271    upvar #0 $handle conn
01272    if {[::info exists conn(searchInProgress)]} {
01273     return $conn(searchInProgress)
01274    } else {
01275         return 0
01276    }       
01277 }
01278 
01279 /* -----------------------------------------------------------------------------*/
01280 /*     searchInit - initiates an LDAP search*/
01281 /* */
01282 /* -----------------------------------------------------------------------------*/
01283 ret  ldap::searchInit ( type handle , type baseObject , type filterString , type attributes , type opt) {
01284     CheckHandle $handle
01285 
01286     upvar #0 $handle conn
01287 
01288     if {[searchInProgress $handle]} {
01289         return -code error \
01290             "Cannot start search. Already a search in progress for this handle."    
01291     }
01292     
01293     set scope        2
01294     set derefAliases 0
01295     set sizeLimit    0
01296     set timeLimit    0
01297     set attrsOnly    0
01298 
01299     foreach {key value} $opt {
01300         switch -- [string tolower $key] {
01301             -scope {
01302                 switch -- $value {
01303                    base         { set scope 0 }
01304                    one - onelevel   { set scope 1 }
01305                    sub - subtree    { set scope 2 }
01306                    default {  }
01307                 }
01308             }
01309         -derefaliases {
01310         switch -- $value {
01311             never   { set derefAliases 0 }
01312             search  { set derefAliases 1 }
01313             find    { set derefAliases 2 }
01314             always  { set derefAliases 3 }
01315             default { }
01316         }
01317         }
01318         -sizelimit {
01319         set sizeLimit $value
01320         }
01321         -timelimit {
01322         set timeLimit $value
01323         }
01324         -attrsonly {
01325         set attrsOnly $value
01326         }
01327         default {
01328         return -code error \
01329             "Invalid search option '$key'"
01330         }
01331         }
01332     }
01333     
01334     set request [buildSearchRequest $baseObject $scope \
01335                 $derefAliases $sizeLimit $timeLimit $attrsOnly $filterString \
01336             $attributes]
01337     set messageId [SendMessage $handle $request]
01338     debugData searchRequest $request
01339     
01340     # Keep the message Id, so we know about the search
01341     set conn(searchInProgress) $messageId
01342 
01343     return $conn(searchInProgress)
01344 }
01345 
01346 ret  ldap::buildSearchRequest (type baseObject , type scope , type derefAliases
01347                        , type sizeLimit , type timeLimit , type attrsOnly , type filterString
01348                    , type attributes) {
01349     #----------------------------------------------------------
01350     #   marshal filter and attributes parameter
01351     #----------------------------------------------------------
01352     regsub -all {\(} $filterString " \{" filterString
01353     regsub -all {\)} $filterString "\} " filterString
01354 
01355     set berFilter [buildUpFilter $filterString]
01356 
01357     set berAttributes ""
01358     foreach attribute $attributes {
01359         append berAttributes [asnOctetString $attribute]
01360     }
01361 
01362     #----------------------------------------------------------
01363     #   marshal search request packet and send it
01364     #----------------------------------------------------------
01365     set request [asnApplicationConstr 3             \
01366                         [asnOctetString $baseObject]    \
01367                         [asnEnumeration $scope]         \
01368                         [asnEnumeration $derefAliases]  \
01369                         [asnInteger     $sizeLimit]     \
01370                         [asnInteger     $timeLimit]     \
01371                         [asnBoolean     $attrsOnly]     \
01372                         $berFilter                      \
01373                         [asnSequence    $berAttributes] \
01374                 ]                                   
01375                 
01376 }
01377 /* -----------------------------------------------------------------------------*/
01378 /*     searchNext - returns the next result of an LDAP search*/
01379 /* */
01380 /* -----------------------------------------------------------------------------*/
01381 ret  ldap::searchNext ( type handle ) {
01382     CheckHandle $handle
01383 
01384     upvar #0 $handle conn
01385 
01386     if {! [::info exists conn(searchInProgress)]} then {
01387     return -code error \
01388         "No search in progress"
01389     }
01390 
01391     set result {}
01392     set lastPacket 0
01393 
01394     #----------------------------------------------------------
01395     #   Wait for a search response packet
01396     #----------------------------------------------------------
01397 
01398     set response [WaitForResponse $handle $conn(searchInProgress)]
01399     debugData searchResponse $response
01400 
01401     asnGetApplication response appNum
01402 
01403     if {$appNum == 4} {
01404         trace "Search Response Continue"
01405     #----------------------------------------------------------
01406     #   unmarshal search data packet
01407     #----------------------------------------------------------
01408     asnGetOctetString response objectName
01409     asnGetSequence    response attributes
01410     set result_attributes {}
01411     while { [string length $attributes] != 0 } {
01412         asnGetSequence attributes attribute
01413         asnGetOctetString attribute attrType
01414         asnGetSet  attribute attrValues
01415         set result_attrValues {}
01416         while { [string length $attrValues] != 0 } {
01417         asnGetOctetString attrValues attrValue
01418         lappend result_attrValues $attrValue
01419         }
01420         lappend result_attributes $attrType $result_attrValues
01421     }
01422     set result [list $objectName $result_attributes]
01423     } elseif {$appNum == 5} {
01424         trace "Search Response Done"
01425     #----------------------------------------------------------
01426     #   unmarshal search final response packet
01427     #----------------------------------------------------------
01428     asnGetEnumeration response resultCode
01429     asnGetOctetString response matchedDN
01430     asnGetOctetString response errorMessage
01431     set result {}
01432     FinalizeMessage $handle $conn(searchInProgress)
01433         unset conn(searchInProgress) 
01434         
01435     if {$resultCode != 0} {
01436         return -code error \
01437         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01438         "LDAP error [resultCode2String $resultCode] : $errorMessage"
01439     }
01440     } else {
01441      error "unexpected application number ($appNum != 4 or 5)"
01442     }
01443 
01444     return $result
01445 }
01446 
01447 /* -----------------------------------------------------------------------------*/
01448 /*     searchEnd - end an LDAP search*/
01449 /* */
01450 /* -----------------------------------------------------------------------------*/
01451 ret  ldap::searchEnd ( type handle ) {
01452     CheckHandle $handle
01453 
01454     upvar #0 $handle conn
01455 
01456     if {! [::info exists conn(searchInProgress)]} then {
01457         # no harm done, just do nothing
01458     return 
01459     }
01460     abandon $handle $conn(searchInProgress) 
01461     FinalizeMessage $handle $conn(searchInProgress)
01462     
01463     unset conn(searchInProgress)
01464     return
01465 }
01466 
01467 /* -----------------------------------------------------------------------------*/
01468 /*  */
01469 /*     Send an LDAP abandon message */
01470 /* */
01471 /* -----------------------------------------------------------------------------    */
01472 ret  ldap::abandon (type handle , type messageId) {
01473     CheckHandle $handle
01474 
01475     upvar #0 $handle conn
01476     trace "MessagesPending: [string length $conn(messageId)]"
01477     set request [asnApplication 16          \
01478                         [asnInteger $messageId]         \
01479                 ]                                   
01480     SendMessageNoReply $handle $request                
01481 }
01482 
01483 /* -----------------------------------------------------------------------------*/
01484 /*     modify  -  provides attribute modifications on one single object (DN):*/
01485 /*                  o replace attributes with new values*/
01486 /*                  o delete attributes (having certain values)*/
01487 /*                  o add attributes with new values*/
01488 /* */
01489 /* -----------------------------------------------------------------------------*/
01490 ret  ldap::modify ( type handle , type dn
01491                     , type attrValToReplace , optional attrToDelete ={ ) { attrValToAdd {} } } {
01492 
01493     CheckHandle $handle
01494 
01495     upvar /* 0 $handle conn*/
01496 
01497      lrep =  {}
01498     foreach {attr value} $attrValToReplace {
01499     lappend lrep $attr [list $value]
01500     }
01501 
01502      ldel =  {}
01503     foreach {attr value} $attrToDelete {
01504     if {[string equal $value ""]} then {
01505         lappend ldel $attr {}
01506     } else {
01507         lappend ldel $attr [list $value]
01508     }
01509     }
01510 
01511      ladd =  {}
01512     foreach {attr value} $attrValToAdd {
01513     lappend ladd $attr [list $value]
01514     }
01515 
01516     modifyMulti $handle $dn $lrep $ldel $ladd
01517 }
01518 
01519 
01520 /* -----------------------------------------------------------------------------*/
01521 /*     modify  -  provides attribute modifications on one single object (DN):*/
01522 /*                  o replace attributes with new values*/
01523 /*                  o delete attributes (having certain values)*/
01524 /*                  o add attributes with new values*/
01525 /* */
01526 /* -----------------------------------------------------------------------------*/
01527 ret  ldap::modifyMulti (type handle , type dn
01528                     , type attrValToReplace , optional attrValToDelete ={) {attrValToAdd {}}} {
01529 
01530     CheckHandle $handle
01531     upvar /* 0 $handle conn*/
01532 
01533      operationAdd =      0
01534      operationDelete =   1
01535      operationReplace =  2
01536 
01537      modifications =  ""
01538 
01539     /* ------------------------------------------------------------------*/
01540     /*    marshal attribute modify operations*/
01541     /*     - always mode 'replace' ! see rfc2251:*/
01542     /* */
01543     /*         replace: replace all existing values of the given attribute*/
01544     /*         with the new values listed, creating the attribute if it*/
01545     /*         did not already exist.  A replace with no value will delete*/
01546     /*         the entire attribute if it exists, and is ignored if the*/
01547     /*         attribute does not exist.*/
01548     /* */
01549     /* ------------------------------------------------------------------*/
01550     append modifications [ldap::packOpAttrVal $operationReplace \
01551                 $attrValToReplace]
01552 
01553     /* ------------------------------------------------------------------*/
01554     /*    marshal attribute add operations*/
01555     /* */
01556     /* ------------------------------------------------------------------*/
01557     append modifications [ldap::packOpAttrVal $operationAdd \
01558                 $attrValToAdd]
01559 
01560     /* ------------------------------------------------------------------*/
01561     /*    marshal attribute delete operations*/
01562     /* */
01563     /*      - a non-empty value will trigger to delete only those*/
01564     /*        attributes which have the same value as the given one*/
01565     /* */
01566     /*      - an empty value will trigger to delete the attribute*/
01567     /*        in all cases*/
01568     /* */
01569     /* ------------------------------------------------------------------*/
01570     append modifications [ldap::packOpAttrVal $operationDelete \
01571                 $attrValToDelete]
01572 
01573     /* ----------------------------------------------------------*/
01574     /*    marshal 'modify' request packet and send it*/
01575     /* ----------------------------------------------------------*/
01576      request =  [asnApplicationConstr 6              \
01577                         [asnOctetString $dn ]            \
01578                         [asnSequence    $modifications ] \
01579                 ]                                    
01580      messageId =  [SendMessage $handle $request]            
01581     debugData modifyRequest $request
01582      response =  [WaitForResponse $handle $messageId]
01583     FinalizeMessage $handle $messageId    
01584     debugData bindResponse $response
01585 
01586     asnGetApplication response appNum
01587     if { $appNum != 7 } {
01588          error "unexpected application number ($appNum != 7)"
01589     }
01590     asnGetEnumeration response resultCode
01591     asnGetOctetString response matchedDN
01592     asnGetOctetString response errorMessage
01593     if {$resultCode != 0} {
01594         return -code error \
01595         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01596         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"    
01597     }
01598 }
01599 
01600 ret  ldap::packOpAttrVal (type op , type attrValueTuples) {
01601     set p ""
01602     foreach {attrName attrValues} $attrValueTuples {
01603     set l {}
01604     foreach v $attrValues {
01605         lappend l [asnOctetString $v]
01606     }
01607         append p [asnSequence                        \
01608             [asnEnumeration $op ]            \
01609             [asnSequence                     \
01610             [asnOctetString $attrName  ] \
01611             [asnSetFromList $l]          \
01612             ]                                \
01613         ]
01614     }
01615     return $p
01616 }
01617 
01618 
01619 /* -----------------------------------------------------------------------------*/
01620 /*     add  -  will create a new object using given DN and sets the given*/
01621 /*             attributes. Multiple value attributes may be used, provided*/
01622 /*             that each attr-val pair be listed.*/
01623 /* */
01624 /* -----------------------------------------------------------------------------*/
01625 ret  ldap::add ( type handle , type dn , type attrValueTuples ) {
01626 
01627     CheckHandle $handle
01628 
01629     #
01630     # In order to handle multi-valuated attributes (see bug 1191326 on
01631     # sourceforge), we walk through tuples to collect all values for
01632     # an attribute.
01633     # http://sourceforge.net/tracker/index.php?func=detail&atid=112883&group_id=12883&aid=1191326
01634     #
01635 
01636     foreach { attrName attrValue } $attrValueTuples {
01637     lappend avpairs($attrName) $attrValue
01638     }
01639 
01640     return [addMulti $handle $dn [array get avpairs]]
01641 }
01642 
01643 /* -----------------------------------------------------------------------------*/
01644 /*     addMulti -  will create a new object using given DN and sets the given*/
01645 /*                 attributes. Argument is a list of attr-listOfVals pair.*/
01646 /* */
01647 /* -----------------------------------------------------------------------------*/
01648 ret  ldap::addMulti ( type handle , type dn , type attrValueTuples ) {
01649 
01650     CheckHandle $handle
01651 
01652     upvar #0 $handle conn
01653 
01654     #------------------------------------------------------------------
01655     #   marshal attribute list
01656     #
01657     #------------------------------------------------------------------
01658     set attrList ""
01659 
01660     foreach { attrName attrValues } $attrValueTuples {
01661     set valList {}
01662     foreach val $attrValues {
01663         lappend valList [asnOctetString $val]
01664     }
01665     append attrList [asnSequence                         \
01666                 [asnOctetString $attrName ]      \
01667                 [asnSetFromList $valList]        \
01668             ]
01669     }
01670 
01671     #----------------------------------------------------------
01672     #   marshal search 'add' request packet and send it
01673     #----------------------------------------------------------
01674     set request [asnApplicationConstr 8             \
01675                         [asnOctetString $dn       ] \
01676                         [asnSequence    $attrList ] \
01677                 ]                               
01678                 
01679     set messageId [SendMessage $handle $request]
01680     debugData addRequest $request
01681     set response [WaitForResponse $handle $messageId]
01682     FinalizeMessage $handle $messageId    
01683     debugData bindResponse $response
01684 
01685     asnGetApplication response appNum
01686     if { $appNum != 9 } {
01687          error "unexpected application number ($appNum != 9)"
01688     }
01689     asnGetEnumeration response resultCode
01690     asnGetOctetString response matchedDN
01691     asnGetOctetString response errorMessage
01692     if {$resultCode != 0} {
01693         return -code error \
01694         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01695         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
01696     }
01697 }
01698 
01699 /* -----------------------------------------------------------------------------*/
01700 /*     delete  -  removes the whole object (DN) inclusive all attributes*/
01701 /* */
01702 /* -----------------------------------------------------------------------------*/
01703 ret  ldap::delete ( type handle , type dn ) {
01704 
01705     CheckHandle $handle
01706 
01707     upvar #0 $handle conn
01708 
01709     #----------------------------------------------------------
01710     #   marshal 'delete' request packet and send it
01711     #----------------------------------------------------------
01712     set request [asnApplication 10 $dn ] 
01713     set messageId [SendMessage $handle $request]
01714     debugData deleteRequest $request
01715     set response [WaitForResponse $handle $messageId]
01716     FinalizeMessage $handle $messageId
01717         
01718     debugData deleteResponse $response
01719 
01720     asnGetApplication response appNum
01721     if { $appNum != 11 } {
01722          error "unexpected application number ($appNum != 11)"
01723     }
01724     asnGetEnumeration response resultCode
01725     asnGetOctetString response matchedDN
01726     asnGetOctetString response errorMessage
01727     if {$resultCode != 0} {
01728         return -code error \
01729         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01730         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
01731     }
01732 }
01733 
01734 
01735 /* -----------------------------------------------------------------------------*/
01736 /*     modifyDN  -  moves an object (DN) to another (relative) place*/
01737 /* */
01738 /* -----------------------------------------------------------------------------*/
01739 ret  ldap::modifyDN ( type handle , type dn , type newrdn , optional deleteOld =1  , optional newSuperior =!  ) {
01740 
01741     CheckHandle $handle
01742 
01743     upvar #0 $handle conn
01744 
01745     #----------------------------------------------------------
01746     #   marshal 'modifyDN' request packet and send it
01747     #----------------------------------------------------------
01748 
01749     if {[string equal $newSuperior "!"]} then {
01750         set request [asnApplicationConstr 12                 \
01751                 [asnOctetString $dn ]            \
01752                 [asnOctetString $newrdn ]        \
01753                 [asnBoolean     $deleteOld ]     \
01754             ]                                         
01755             
01756     } else {
01757     set request [asnApplicationConstr 12                 \
01758                 [asnOctetString $dn ]            \
01759                 [asnOctetString $newrdn ]        \
01760                 [asnBoolean     $deleteOld ]     \
01761                 [asnContext     0 $newSuperior]  \
01762             ]                                       
01763     }
01764     set messageId [SendMessage $handle $request]
01765     debugData modifyRequest $request
01766     set response [WaitForResponse $handle $messageId]
01767 
01768     asnGetApplication response appNum
01769     if { $appNum != 13 } {
01770          error "unexpected application number ($appNum != 13)"
01771     }
01772     asnGetEnumeration response resultCode
01773     asnGetOctetString response matchedDN
01774     asnGetOctetString response errorMessage
01775     if {$resultCode != 0} {
01776         return -code error \
01777         -errorcode [list LDAP [resultCode2String $resultCode] $matchedDN $errorMessage] \
01778         "LDAP error [resultCode2String $resultCode] '$matchedDN': $errorMessage"
01779 
01780     }
01781 }
01782 
01783 /* -----------------------------------------------------------------------------*/
01784 /*     disconnect*/
01785 /* */
01786 /* -----------------------------------------------------------------------------*/
01787 ret  ldap::disconnect ( type handle ) {
01788 
01789     CheckHandle $handle
01790     
01791     upvar #0 $handle conn
01792 
01793     # should we sent an 'unbind' ?
01794     catch {close $conn(sock)}
01795     unset conn
01796 
01797     return
01798 }
01799 
01800 
01801 
01802 /* -----------------------------------------------------------------------------*/
01803 /*     trace*/
01804 /* */
01805 /* -----------------------------------------------------------------------------*/
01806 ret  ldap::trace ( type message ) {
01807 
01808     variable doDebug
01809 
01810     if {!$doDebug} return
01811 
01812     puts stderr $message
01813 }
01814 
01815 
01816 /* -----------------------------------------------------------------------------*/
01817 /*     debugData*/
01818 /* */
01819 /* -----------------------------------------------------------------------------*/
01820 ret  ldap::debugData ( type info , type data ) {
01821 
01822     variable doDebug
01823 
01824     if {!$doDebug} return
01825 
01826     set len [string length $data]
01827     trace "$info ($len bytes):"
01828     set address ""
01829     set hexnums ""
01830     set ascii   ""
01831     for {set i 0} {$i < $len} {incr i} {
01832         set v [string index $data $i]
01833         binary scan $v H2 hex
01834         binary scan $v c  num
01835         set num [expr {( $num + 0x100 ) % 0x100}]
01836         set text .
01837         if {$num > 31} {
01838             set text $v
01839         }
01840         if { ($i % 16) == 0 } {
01841             if {$address != ""} {
01842                 trace [format "%4s  %-48s  |%s|" $address $hexnums $ascii ]
01843                 set address ""
01844                 set hexnums ""
01845                 set ascii   ""
01846             }
01847             append address [format "%04d" $i]
01848         }
01849         append hexnums "$hex "
01850         append ascii   $text
01851         #trace [format "%3d %2s %s" $i $hex $text]
01852     }
01853     if {$address != ""} {
01854         trace [format "%4s  %-48s  |%s|" $address $hexnums $ascii ]
01855     }
01856     trace ""
01857 }
01858 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1