sl@0: # safe.tcl -- sl@0: # sl@0: # This file provide a safe loading/sourcing mechanism for safe interpreters. sl@0: # It implements a virtual path mecanism to hide the real pathnames from the sl@0: # slave. It runs in a master interpreter and sets up data structure and sl@0: # aliases that will be invoked when used from a slave interpreter. sl@0: # sl@0: # See the safe.n man page for details. sl@0: # sl@0: # Copyright (c) 1996-1997 Sun Microsystems, Inc. sl@0: # sl@0: # See the file "license.terms" for information on usage and redistribution sl@0: # of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: # sl@0: # RCS: @(#) $Id: safe.tcl,v 1.9.2.3 2005/07/22 21:59:41 dgp Exp $ sl@0: sl@0: # sl@0: # The implementation is based on namespaces. These naming conventions sl@0: # are followed: sl@0: # Private procs starts with uppercase. sl@0: # Public procs are exported and starts with lowercase sl@0: # sl@0: sl@0: # Needed utilities package sl@0: package require opt 0.4.1; sl@0: sl@0: # Create the safe namespace sl@0: namespace eval ::safe { sl@0: sl@0: # Exported API: sl@0: namespace export interpCreate interpInit interpConfigure interpDelete \ sl@0: interpAddToAccessPath interpFindInAccessPath setLogCmd sl@0: sl@0: #### sl@0: # sl@0: # Setup the arguments parsing sl@0: # sl@0: #### sl@0: sl@0: # Make sure that our temporary variable is local to this sl@0: # namespace. [Bug 981733] sl@0: variable temp sl@0: sl@0: # Share the descriptions sl@0: set temp [::tcl::OptKeyRegister { sl@0: {-accessPath -list {} "access path for the slave"} sl@0: {-noStatics "prevent loading of statically linked pkgs"} sl@0: {-statics true "loading of statically linked pkgs"} sl@0: {-nestedLoadOk "allow nested loading"} sl@0: {-nested false "nested loading"} sl@0: {-deleteHook -script {} "delete hook"} sl@0: }] sl@0: sl@0: # create case (slave is optional) sl@0: ::tcl::OptKeyRegister { sl@0: {?slave? -name {} "name of the slave (optional)"} sl@0: } ::safe::interpCreate sl@0: # adding the flags sub programs to the command program sl@0: # (relying on Opt's internal implementation details) sl@0: lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) sl@0: sl@0: # init and configure (slave is needed) sl@0: ::tcl::OptKeyRegister { sl@0: {slave -name {} "name of the slave"} sl@0: } ::safe::interpIC sl@0: # adding the flags sub programs to the command program sl@0: # (relying on Opt's internal implementation details) sl@0: lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) sl@0: # temp not needed anymore sl@0: ::tcl::OptKeyDelete $temp sl@0: sl@0: sl@0: # Helper function to resolve the dual way of specifying staticsok sl@0: # (either by -noStatics or -statics 0) sl@0: proc InterpStatics {} { sl@0: foreach v {Args statics noStatics} { sl@0: upvar $v $v sl@0: } sl@0: set flag [::tcl::OptProcArgGiven -noStatics]; sl@0: if {$flag && (!$noStatics == !$statics) sl@0: && ([::tcl::OptProcArgGiven -statics])} { sl@0: return -code error\ sl@0: "conflicting values given for -statics and -noStatics" sl@0: } sl@0: if {$flag} { sl@0: return [expr {!$noStatics}] sl@0: } else { sl@0: return $statics sl@0: } sl@0: } sl@0: sl@0: # Helper function to resolve the dual way of specifying nested loading sl@0: # (either by -nestedLoadOk or -nested 1) sl@0: proc InterpNested {} { sl@0: foreach v {Args nested nestedLoadOk} { sl@0: upvar $v $v sl@0: } sl@0: set flag [::tcl::OptProcArgGiven -nestedLoadOk]; sl@0: # note that the test here is the opposite of the "InterpStatics" sl@0: # one (it is not -noNested... because of the wanted default value) sl@0: if {$flag && (!$nestedLoadOk != !$nested) sl@0: && ([::tcl::OptProcArgGiven -nested])} { sl@0: return -code error\ sl@0: "conflicting values given for -nested and -nestedLoadOk" sl@0: } sl@0: if {$flag} { sl@0: # another difference with "InterpStatics" sl@0: return $nestedLoadOk sl@0: } else { sl@0: return $nested sl@0: } sl@0: } sl@0: sl@0: #### sl@0: # sl@0: # API entry points that needs argument parsing : sl@0: # sl@0: #### sl@0: sl@0: sl@0: # Interface/entry point function and front end for "Create" sl@0: proc interpCreate {args} { sl@0: set Args [::tcl::OptKeyParse ::safe::interpCreate $args] sl@0: InterpCreate $slave $accessPath \ sl@0: [InterpStatics] [InterpNested] $deleteHook sl@0: } sl@0: sl@0: proc interpInit {args} { sl@0: set Args [::tcl::OptKeyParse ::safe::interpIC $args] sl@0: if {![::interp exists $slave]} { sl@0: return -code error "\"$slave\" is not an interpreter" sl@0: } sl@0: InterpInit $slave $accessPath \ sl@0: [InterpStatics] [InterpNested] $deleteHook; sl@0: } sl@0: sl@0: proc CheckInterp {slave} { sl@0: if {![IsInterp $slave]} { sl@0: return -code error \ sl@0: "\"$slave\" is not an interpreter managed by ::safe::" sl@0: } sl@0: } sl@0: sl@0: # Interface/entry point function and front end for "Configure" sl@0: # This code is awfully pedestrian because it would need sl@0: # more coupling and support between the way we store the sl@0: # configuration values in safe::interp's and the Opt package sl@0: # Obviously we would like an OptConfigure sl@0: # to avoid duplicating all this code everywhere. -> TODO sl@0: # (the app should share or access easily the program/value sl@0: # stored by opt) sl@0: # This is even more complicated by the boolean flags with no values sl@0: # that we had the bad idea to support for the sake of user simplicity sl@0: # in create/init but which makes life hard in configure... sl@0: # So this will be hopefully written and some integrated with opt1.0 sl@0: # (hopefully for tcl8.1 ?) sl@0: proc interpConfigure {args} { sl@0: switch [llength $args] { sl@0: 1 { sl@0: # If we have exactly 1 argument sl@0: # the semantic is to return all the current configuration sl@0: # We still call OptKeyParse though we know that "slave" sl@0: # is our given argument because it also checks sl@0: # for the "-help" option. sl@0: set Args [::tcl::OptKeyParse ::safe::interpIC $args] sl@0: CheckInterp $slave sl@0: set res {} sl@0: lappend res [list -accessPath [Set [PathListName $slave]]] sl@0: lappend res [list -statics [Set [StaticsOkName $slave]]] sl@0: lappend res [list -nested [Set [NestedOkName $slave]]] sl@0: lappend res [list -deleteHook [Set [DeleteHookName $slave]]] sl@0: join $res sl@0: } sl@0: 2 { sl@0: # If we have exactly 2 arguments sl@0: # the semantic is a "configure get" sl@0: ::tcl::Lassign $args slave arg sl@0: # get the flag sub program (we 'know' about Opt's internal sl@0: # representation of data) sl@0: set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] sl@0: set hits [::tcl::OptHits desc $arg] sl@0: if {$hits > 1} { sl@0: return -code error [::tcl::OptAmbigous $desc $arg] sl@0: } elseif {$hits == 0} { sl@0: return -code error [::tcl::OptFlagUsage $desc $arg] sl@0: } sl@0: CheckInterp $slave sl@0: set item [::tcl::OptCurDesc $desc] sl@0: set name [::tcl::OptName $item] sl@0: switch -exact -- $name { sl@0: -accessPath { sl@0: return [list -accessPath [Set [PathListName $slave]]] sl@0: } sl@0: -statics { sl@0: return [list -statics [Set [StaticsOkName $slave]]] sl@0: } sl@0: -nested { sl@0: return [list -nested [Set [NestedOkName $slave]]] sl@0: } sl@0: -deleteHook { sl@0: return [list -deleteHook [Set [DeleteHookName $slave]]] sl@0: } sl@0: -noStatics { sl@0: # it is most probably a set in fact sl@0: # but we would need then to jump to the set part sl@0: # and it is not *sure* that it is a set action sl@0: # that the user want, so force it to use the sl@0: # unambigous -statics ?value? instead: sl@0: return -code error\ sl@0: "ambigous query (get or set -noStatics ?)\ sl@0: use -statics instead" sl@0: } sl@0: -nestedLoadOk { sl@0: return -code error\ sl@0: "ambigous query (get or set -nestedLoadOk ?)\ sl@0: use -nested instead" sl@0: } sl@0: default { sl@0: return -code error "unknown flag $name (bug)" sl@0: } sl@0: } sl@0: } sl@0: default { sl@0: # Otherwise we want to parse the arguments like init and create sl@0: # did sl@0: set Args [::tcl::OptKeyParse ::safe::interpIC $args] sl@0: CheckInterp $slave sl@0: # Get the current (and not the default) values of sl@0: # whatever has not been given: sl@0: if {![::tcl::OptProcArgGiven -accessPath]} { sl@0: set doreset 1 sl@0: set accessPath [Set [PathListName $slave]] sl@0: } else { sl@0: set doreset 0 sl@0: } sl@0: if {(![::tcl::OptProcArgGiven -statics]) \ sl@0: && (![::tcl::OptProcArgGiven -noStatics]) } { sl@0: set statics [Set [StaticsOkName $slave]] sl@0: } else { sl@0: set statics [InterpStatics] sl@0: } sl@0: if {([::tcl::OptProcArgGiven -nested]) \ sl@0: || ([::tcl::OptProcArgGiven -nestedLoadOk]) } { sl@0: set nested [InterpNested] sl@0: } else { sl@0: set nested [Set [NestedOkName $slave]] sl@0: } sl@0: if {![::tcl::OptProcArgGiven -deleteHook]} { sl@0: set deleteHook [Set [DeleteHookName $slave]] sl@0: } sl@0: # we can now reconfigure : sl@0: InterpSetConfig $slave $accessPath $statics $nested $deleteHook sl@0: # auto_reset the slave (to completly synch the new access_path) sl@0: if {$doreset} { sl@0: if {[catch {::interp eval $slave {auto_reset}} msg]} { sl@0: Log $slave "auto_reset failed: $msg" sl@0: } else { sl@0: Log $slave "successful auto_reset" NOTICE sl@0: } sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: #### sl@0: # sl@0: # Functions that actually implements the exported APIs sl@0: # sl@0: #### sl@0: sl@0: sl@0: # sl@0: # safe::InterpCreate : doing the real job sl@0: # sl@0: # This procedure creates a safe slave and initializes it with the sl@0: # safe base aliases. sl@0: # NB: slave name must be simple alphanumeric string, no spaces, sl@0: # no (), no {},... {because the state array is stored as part of the name} sl@0: # sl@0: # Returns the slave name. sl@0: # sl@0: # Optional Arguments : sl@0: # + slave name : if empty, generated name will be used sl@0: # + access_path: path list controlling where load/source can occur, sl@0: # if empty: the master auto_path will be used. sl@0: # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) sl@0: # if 1 :static packages are ok. sl@0: # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) sl@0: # if 1 : multiple levels are ok. sl@0: sl@0: # use the full name and no indent so auto_mkIndex can find us sl@0: proc ::safe::InterpCreate { sl@0: slave sl@0: access_path sl@0: staticsok sl@0: nestedok sl@0: deletehook sl@0: } { sl@0: # Create the slave. sl@0: if {$slave ne ""} { sl@0: ::interp create -safe $slave sl@0: } else { sl@0: # empty argument: generate slave name sl@0: set slave [::interp create -safe] sl@0: } sl@0: Log $slave "Created" NOTICE sl@0: sl@0: # Initialize it. (returns slave name) sl@0: InterpInit $slave $access_path $staticsok $nestedok $deletehook sl@0: } sl@0: sl@0: sl@0: # sl@0: # InterpSetConfig (was setAccessPath) : sl@0: # Sets up slave virtual auto_path and corresponding structure sl@0: # within the master. Also sets the tcl_library in the slave sl@0: # to be the first directory in the path. sl@0: # Nb: If you change the path after the slave has been initialized sl@0: # you probably need to call "auto_reset" in the slave in order that it sl@0: # gets the right auto_index() array values. sl@0: sl@0: proc ::safe::InterpSetConfig {slave access_path staticsok\ sl@0: nestedok deletehook} { sl@0: sl@0: # determine and store the access path if empty sl@0: if {$access_path eq ""} { sl@0: set access_path [uplevel \#0 set auto_path] sl@0: # Make sure that tcl_library is in auto_path sl@0: # and at the first position (needed by setAccessPath) sl@0: set where [lsearch -exact $access_path [info library]] sl@0: if {$where == -1} { sl@0: # not found, add it. sl@0: set access_path [concat [list [info library]] $access_path] sl@0: Log $slave "tcl_library was not in auto_path,\ sl@0: added it to slave's access_path" NOTICE sl@0: } elseif {$where != 0} { sl@0: # not first, move it first sl@0: set access_path [concat [list [info library]]\ sl@0: [lreplace $access_path $where $where]] sl@0: Log $slave "tcl_libray was not in first in auto_path,\ sl@0: moved it to front of slave's access_path" NOTICE sl@0: sl@0: } sl@0: sl@0: # Add 1st level sub dirs (will searched by auto loading from tcl sl@0: # code in the slave using glob and thus fail, so we add them sl@0: # here so by default it works the same). sl@0: set access_path [AddSubDirs $access_path] sl@0: } sl@0: sl@0: Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\ sl@0: nestedok=$nestedok deletehook=($deletehook)" NOTICE sl@0: sl@0: # clear old autopath if it existed sl@0: set nname [PathNumberName $slave] sl@0: if {[Exists $nname]} { sl@0: set n [Set $nname] sl@0: for {set i 0} {$i<$n} {incr i} { sl@0: Unset [PathToken $i $slave] sl@0: } sl@0: } sl@0: sl@0: # build new one sl@0: set slave_auto_path {} sl@0: set i 0 sl@0: foreach dir $access_path { sl@0: Set [PathToken $i $slave] $dir sl@0: lappend slave_auto_path "\$[PathToken $i]" sl@0: incr i sl@0: } sl@0: Set $nname $i sl@0: Set [PathListName $slave] $access_path sl@0: Set [VirtualPathListName $slave] $slave_auto_path sl@0: sl@0: Set [StaticsOkName $slave] $staticsok sl@0: Set [NestedOkName $slave] $nestedok sl@0: Set [DeleteHookName $slave] $deletehook sl@0: sl@0: SyncAccessPath $slave sl@0: } sl@0: sl@0: # sl@0: # sl@0: # FindInAccessPath: sl@0: # Search for a real directory and returns its virtual Id sl@0: # (including the "$") sl@0: proc ::safe::interpFindInAccessPath {slave path} { sl@0: set access_path [GetAccessPath $slave] sl@0: set where [lsearch -exact $access_path $path] sl@0: if {$where == -1} { sl@0: return -code error "$path not found in access path $access_path" sl@0: } sl@0: return "\$[PathToken $where]" sl@0: } sl@0: sl@0: # sl@0: # addToAccessPath: sl@0: # add (if needed) a real directory to access path sl@0: # and return its virtual token (including the "$"). sl@0: proc ::safe::interpAddToAccessPath {slave path} { sl@0: # first check if the directory is already in there sl@0: if {![catch {interpFindInAccessPath $slave $path} res]} { sl@0: return $res sl@0: } sl@0: # new one, add it: sl@0: set nname [PathNumberName $slave] sl@0: set n [Set $nname] sl@0: Set [PathToken $n $slave] $path sl@0: sl@0: set token "\$[PathToken $n]" sl@0: sl@0: Lappend [VirtualPathListName $slave] $token sl@0: Lappend [PathListName $slave] $path sl@0: Set $nname [expr {$n+1}] sl@0: sl@0: SyncAccessPath $slave sl@0: sl@0: return $token sl@0: } sl@0: sl@0: # This procedure applies the initializations to an already existing sl@0: # interpreter. It is useful when you want to install the safe base sl@0: # aliases into a preexisting safe interpreter. sl@0: proc ::safe::InterpInit { sl@0: slave sl@0: access_path sl@0: staticsok sl@0: nestedok sl@0: deletehook sl@0: } { sl@0: sl@0: # Configure will generate an access_path when access_path is sl@0: # empty. sl@0: InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook sl@0: sl@0: # These aliases let the slave load files to define new commands sl@0: sl@0: # NB we need to add [namespace current], aliases are always sl@0: # absolute paths. sl@0: ::interp alias $slave source {} [namespace current]::AliasSource $slave sl@0: ::interp alias $slave load {} [namespace current]::AliasLoad $slave sl@0: sl@0: # This alias lets the slave use the encoding names, convertfrom, sl@0: # convertto, and system, but not "encoding system " to set sl@0: # the system encoding. sl@0: sl@0: ::interp alias $slave encoding {} [namespace current]::AliasEncoding \ sl@0: $slave sl@0: sl@0: # This alias lets the slave have access to a subset of the 'file' sl@0: # command functionality. sl@0: sl@0: AliasSubset $slave file file dir.* join root.* ext.* tail \ sl@0: path.* split sl@0: sl@0: # This alias interposes on the 'exit' command and cleanly terminates sl@0: # the slave. sl@0: sl@0: ::interp alias $slave exit {} [namespace current]::interpDelete $slave sl@0: sl@0: # The allowed slave variables already have been set sl@0: # by Tcl_MakeSafe(3) sl@0: sl@0: sl@0: # Source init.tcl into the slave, to get auto_load and other sl@0: # procedures defined: sl@0: sl@0: # We don't try to use the -rsrc on the mac because it would get sl@0: # confusing if you would want to customize init.tcl sl@0: # for a given set of safe slaves, on all the platforms sl@0: # you just need to give a specific access_path and sl@0: # the mac should be no exception. As there is no sl@0: # obvious full "safe ressources" design nor implementation sl@0: # for the mac, safe interps there will just don't sl@0: # have that ability. (A specific app can still reenable sl@0: # that using custom aliases if they want to). sl@0: # It would also make the security analysis and the Safe Tcl security sl@0: # model platform dependant and thus more error prone. sl@0: sl@0: if {[catch {::interp eval $slave\ sl@0: {source [file join $tcl_library init.tcl]}} msg]} { sl@0: Log $slave "can't source init.tcl ($msg)" sl@0: error "can't source init.tcl into slave $slave ($msg)" sl@0: } sl@0: sl@0: return $slave sl@0: } sl@0: sl@0: sl@0: # Add (only if needed, avoid duplicates) 1 level of sl@0: # sub directories to an existing path list. sl@0: # Also removes non directories from the returned list. sl@0: proc AddSubDirs {pathList} { sl@0: set res {} sl@0: foreach dir $pathList { sl@0: if {[file isdirectory $dir]} { sl@0: # check that we don't have it yet as a children sl@0: # of a previous dir sl@0: if {[lsearch -exact $res $dir]<0} { sl@0: lappend res $dir sl@0: } sl@0: foreach sub [glob -directory $dir -nocomplain *] { sl@0: if {([file isdirectory $sub]) \ sl@0: && ([lsearch -exact $res $sub]<0) } { sl@0: # new sub dir, add it ! sl@0: lappend res $sub sl@0: } sl@0: } sl@0: } sl@0: } sl@0: return $res sl@0: } sl@0: sl@0: # This procedure deletes a safe slave managed by Safe Tcl and sl@0: # cleans up associated state: sl@0: sl@0: proc ::safe::interpDelete {slave} { sl@0: sl@0: Log $slave "About to delete" NOTICE sl@0: sl@0: # If the slave has a cleanup hook registered, call it. sl@0: # check the existance because we might be called to delete an interp sl@0: # which has not been registered with us at all sl@0: set hookname [DeleteHookName $slave] sl@0: if {[Exists $hookname]} { sl@0: set hook [Set $hookname] sl@0: if {![::tcl::Lempty $hook]} { sl@0: # remove the hook now, otherwise if the hook sl@0: # calls us somehow, we'll loop sl@0: Unset $hookname sl@0: if {[catch {eval $hook [list $slave]} err]} { sl@0: Log $slave "Delete hook error ($err)" sl@0: } sl@0: } sl@0: } sl@0: sl@0: # Discard the global array of state associated with the slave, and sl@0: # delete the interpreter. sl@0: sl@0: set statename [InterpStateName $slave] sl@0: if {[Exists $statename]} { sl@0: Unset $statename sl@0: } sl@0: sl@0: # if we have been called twice, the interp might have been deleted sl@0: # already sl@0: if {[::interp exists $slave]} { sl@0: ::interp delete $slave sl@0: Log $slave "Deleted" NOTICE sl@0: } sl@0: sl@0: return sl@0: } sl@0: sl@0: # Set (or get) the loging mecanism sl@0: sl@0: proc ::safe::setLogCmd {args} { sl@0: variable Log sl@0: if {[llength $args] == 0} { sl@0: return $Log sl@0: } else { sl@0: if {[llength $args] == 1} { sl@0: set Log [lindex $args 0] sl@0: } else { sl@0: set Log $args sl@0: } sl@0: } sl@0: } sl@0: sl@0: # internal variable sl@0: variable Log {} sl@0: sl@0: # ------------------- END OF PUBLIC METHODS ------------ sl@0: sl@0: sl@0: # sl@0: # sets the slave auto_path to the master recorded value. sl@0: # also sets tcl_library to the first token of the virtual path. sl@0: # sl@0: proc SyncAccessPath {slave} { sl@0: set slave_auto_path [Set [VirtualPathListName $slave]] sl@0: ::interp eval $slave [list set auto_path $slave_auto_path] sl@0: Log $slave "auto_path in $slave has been set to $slave_auto_path"\ sl@0: NOTICE sl@0: ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]] sl@0: } sl@0: sl@0: # base name for storing all the slave states sl@0: # the array variable name for slave foo is thus "Sfoo" sl@0: # and for sub slave {foo bar} "Sfoo bar" (spaces are handled sl@0: # ok everywhere (or should)) sl@0: # We add the S prefix to avoid that a slave interp called "Log" sl@0: # would smash our "Log" variable. sl@0: proc InterpStateName {slave} { sl@0: return "S$slave" sl@0: } sl@0: sl@0: # Check that the given slave is "one of us" sl@0: proc IsInterp {slave} { sl@0: expr {[Exists [InterpStateName $slave]] && [::interp exists $slave]} sl@0: } sl@0: sl@0: # returns the virtual token for directory number N sl@0: # if the slave argument is given, sl@0: # it will return the corresponding master global variable name sl@0: proc PathToken {n {slave ""}} { sl@0: if {$slave ne ""} { sl@0: return "[InterpStateName $slave](access_path,$n)" sl@0: } else { sl@0: # We need to have a ":" in the token string so sl@0: # [file join] on the mac won't turn it into a relative sl@0: # path. sl@0: return "p(:$n:)" sl@0: } sl@0: } sl@0: # returns the variable name of the complete path list sl@0: proc PathListName {slave} { sl@0: return "[InterpStateName $slave](access_path)" sl@0: } sl@0: # returns the variable name of the complete path list sl@0: proc VirtualPathListName {slave} { sl@0: return "[InterpStateName $slave](access_path_slave)" sl@0: } sl@0: # returns the variable name of the number of items sl@0: proc PathNumberName {slave} { sl@0: return "[InterpStateName $slave](access_path,n)" sl@0: } sl@0: # returns the staticsok flag var name sl@0: proc StaticsOkName {slave} { sl@0: return "[InterpStateName $slave](staticsok)" sl@0: } sl@0: # returns the nestedok flag var name sl@0: proc NestedOkName {slave} { sl@0: return "[InterpStateName $slave](nestedok)" sl@0: } sl@0: # Run some code at the namespace toplevel sl@0: proc Toplevel {args} { sl@0: namespace eval [namespace current] $args sl@0: } sl@0: # set/get values sl@0: proc Set {args} { sl@0: eval [linsert $args 0 Toplevel set] sl@0: } sl@0: # lappend on toplevel vars sl@0: proc Lappend {args} { sl@0: eval [linsert $args 0 Toplevel lappend] sl@0: } sl@0: # unset a var/token (currently just an global level eval) sl@0: proc Unset {args} { sl@0: eval [linsert $args 0 Toplevel unset] sl@0: } sl@0: # test existance sl@0: proc Exists {varname} { sl@0: Toplevel info exists $varname sl@0: } sl@0: # short cut for access path getting sl@0: proc GetAccessPath {slave} { sl@0: Set [PathListName $slave] sl@0: } sl@0: # short cut for statics ok flag getting sl@0: proc StaticsOk {slave} { sl@0: Set [StaticsOkName $slave] sl@0: } sl@0: # short cut for getting the multiples interps sub loading ok flag sl@0: proc NestedOk {slave} { sl@0: Set [NestedOkName $slave] sl@0: } sl@0: # interp deletion storing hook name sl@0: proc DeleteHookName {slave} { sl@0: return [InterpStateName $slave](cleanupHook) sl@0: } sl@0: sl@0: # sl@0: # translate virtual path into real path sl@0: # sl@0: proc TranslatePath {slave path} { sl@0: # somehow strip the namespaces 'functionality' out (the danger sl@0: # is that we would strip valid macintosh "../" queries... : sl@0: if {[regexp {(::)|(\.\.)} $path]} { sl@0: error "invalid characters in path $path" sl@0: } sl@0: set n [expr {[Set [PathNumberName $slave]]-1}] sl@0: for {} {$n>=0} {incr n -1} { sl@0: # fill the token virtual names with their real value sl@0: set [PathToken $n] [Set [PathToken $n $slave]] sl@0: } sl@0: # replaces the token by their value sl@0: subst -nobackslashes -nocommands $path sl@0: } sl@0: sl@0: sl@0: # Log eventually log an error sl@0: # to enable error logging, set Log to {puts stderr} for instance sl@0: proc Log {slave msg {type ERROR}} { sl@0: variable Log sl@0: if {[info exists Log] && [llength $Log]} { sl@0: eval $Log [list "$type for slave $slave : $msg"] sl@0: } sl@0: } sl@0: sl@0: sl@0: # file name control (limit access to files/ressources that should be sl@0: # a valid tcl source file) sl@0: proc CheckFileName {slave file} { sl@0: # This used to limit what can be sourced to ".tcl" and forbid files sl@0: # with more than 1 dot and longer than 14 chars, but I changed that sl@0: # for 8.4 as a safe interp has enough internal protection already sl@0: # to allow sourcing anything. - hobbs sl@0: sl@0: if {![file exists $file]} { sl@0: # don't tell the file path sl@0: error "no such file or directory" sl@0: } sl@0: sl@0: if {![file readable $file]} { sl@0: # don't tell the file path sl@0: error "not readable" sl@0: } sl@0: } sl@0: sl@0: sl@0: # AliasSource is the target of the "source" alias in safe interpreters. sl@0: sl@0: proc AliasSource {slave args} { sl@0: sl@0: set argc [llength $args] sl@0: # Allow only "source filename" sl@0: # (and not mac specific -rsrc for instance - see comment in ::init sl@0: # for current rationale) sl@0: if {$argc != 1} { sl@0: set msg "wrong # args: should be \"source fileName\"" sl@0: Log $slave "$msg ($args)" sl@0: return -code error $msg sl@0: } sl@0: set file [lindex $args 0] sl@0: sl@0: # get the real path from the virtual one. sl@0: if {[catch {set file [TranslatePath $slave $file]} msg]} { sl@0: Log $slave $msg sl@0: return -code error "permission denied" sl@0: } sl@0: sl@0: # check that the path is in the access path of that slave sl@0: if {[catch {FileInAccessPath $slave $file} msg]} { sl@0: Log $slave $msg sl@0: return -code error "permission denied" sl@0: } sl@0: sl@0: # do the checks on the filename : sl@0: if {[catch {CheckFileName $slave $file} msg]} { sl@0: Log $slave "$file:$msg" sl@0: return -code error $msg sl@0: } sl@0: sl@0: # passed all the tests , lets source it: sl@0: if {[catch {::interp invokehidden $slave source $file} msg]} { sl@0: Log $slave $msg sl@0: return -code error "script error" sl@0: } sl@0: return $msg sl@0: } sl@0: sl@0: # AliasLoad is the target of the "load" alias in safe interpreters. sl@0: sl@0: proc AliasLoad {slave file args} { sl@0: sl@0: set argc [llength $args] sl@0: if {$argc > 2} { sl@0: set msg "load error: too many arguments" sl@0: Log $slave "$msg ($argc) {$file $args}" sl@0: return -code error $msg sl@0: } sl@0: sl@0: # package name (can be empty if file is not). sl@0: set package [lindex $args 0] sl@0: sl@0: # Determine where to load. load use a relative interp path sl@0: # and {} means self, so we can directly and safely use passed arg. sl@0: set target [lindex $args 1] sl@0: if {$target ne ""} { sl@0: # we will try to load into a sub sub interp sl@0: # check that we want to authorize that. sl@0: if {![NestedOk $slave]} { sl@0: Log $slave "loading to a sub interp (nestedok)\ sl@0: disabled (trying to load $package to $target)" sl@0: return -code error "permission denied (nested load)" sl@0: } sl@0: sl@0: } sl@0: sl@0: # Determine what kind of load is requested sl@0: if {$file eq ""} { sl@0: # static package loading sl@0: if {$package eq ""} { sl@0: set msg "load error: empty filename and no package name" sl@0: Log $slave $msg sl@0: return -code error $msg sl@0: } sl@0: if {![StaticsOk $slave]} { sl@0: Log $slave "static packages loading disabled\ sl@0: (trying to load $package to $target)" sl@0: return -code error "permission denied (static package)" sl@0: } sl@0: } else { sl@0: # file loading sl@0: sl@0: # get the real path from the virtual one. sl@0: if {[catch {set file [TranslatePath $slave $file]} msg]} { sl@0: Log $slave $msg sl@0: return -code error "permission denied" sl@0: } sl@0: sl@0: # check the translated path sl@0: if {[catch {FileInAccessPath $slave $file} msg]} { sl@0: Log $slave $msg sl@0: return -code error "permission denied (path)" sl@0: } sl@0: } sl@0: sl@0: if {[catch {::interp invokehidden\ sl@0: $slave load $file $package $target} msg]} { sl@0: Log $slave $msg sl@0: return -code error $msg sl@0: } sl@0: sl@0: return $msg sl@0: } sl@0: sl@0: # FileInAccessPath raises an error if the file is not found in sl@0: # the list of directories contained in the (master side recorded) slave's sl@0: # access path. sl@0: sl@0: # the security here relies on "file dirname" answering the proper sl@0: # result.... needs checking ? sl@0: proc FileInAccessPath {slave file} { sl@0: sl@0: set access_path [GetAccessPath $slave] sl@0: sl@0: if {[file isdirectory $file]} { sl@0: error "\"$file\": is a directory" sl@0: } sl@0: set parent [file dirname $file] sl@0: sl@0: # Normalize paths for comparison since lsearch knows nothing of sl@0: # potential pathname anomalies. sl@0: set norm_parent [file normalize $parent] sl@0: foreach path $access_path { sl@0: lappend norm_access_path [file normalize $path] sl@0: } sl@0: sl@0: if {[lsearch -exact $norm_access_path $norm_parent] == -1} { sl@0: error "\"$file\": not in access_path" sl@0: } sl@0: } sl@0: sl@0: # This procedure enables access from a safe interpreter to only a subset of sl@0: # the subcommands of a command: sl@0: sl@0: proc Subset {slave command okpat args} { sl@0: set subcommand [lindex $args 0] sl@0: if {[regexp $okpat $subcommand]} { sl@0: return [eval [linsert $args 0 $command]] sl@0: } sl@0: set msg "not allowed to invoke subcommand $subcommand of $command" sl@0: Log $slave $msg sl@0: error $msg sl@0: } sl@0: sl@0: # This procedure installs an alias in a slave that invokes "safesubset" sl@0: # in the master to execute allowed subcommands. It precomputes the pattern sl@0: # of allowed subcommands; you can use wildcards in the pattern if you wish sl@0: # to allow subcommand abbreviation. sl@0: # sl@0: # Syntax is: AliasSubset slave alias target subcommand1 subcommand2... sl@0: sl@0: proc AliasSubset {slave alias target args} { sl@0: set pat ^(; set sep "" sl@0: foreach sub $args { sl@0: append pat $sep$sub sl@0: set sep | sl@0: } sl@0: append pat )\$ sl@0: ::interp alias $slave $alias {}\ sl@0: [namespace current]::Subset $slave $target $pat sl@0: } sl@0: sl@0: # AliasEncoding is the target of the "encoding" alias in safe interpreters. sl@0: sl@0: proc AliasEncoding {slave args} { sl@0: sl@0: set argc [llength $args] sl@0: sl@0: set okpat "^(name.*|convert.*)\$" sl@0: set subcommand [lindex $args 0] sl@0: sl@0: if {[regexp $okpat $subcommand]} { sl@0: return [eval [linsert $args 0 \ sl@0: ::interp invokehidden $slave encoding]] sl@0: } sl@0: sl@0: if {[string first $subcommand system] == 0} { sl@0: if {$argc == 1} { sl@0: # passed all the tests , lets source it: sl@0: if {[catch {::interp invokehidden \ sl@0: $slave encoding system} msg]} { sl@0: Log $slave $msg sl@0: return -code error "script error" sl@0: } sl@0: } else { sl@0: set msg "wrong # args: should be \"encoding system\"" sl@0: Log $slave $msg sl@0: error $msg sl@0: } sl@0: } else { sl@0: set msg "wrong # args: should be \"encoding option ?arg ...?\"" sl@0: Log $slave $msg sl@0: error $msg sl@0: } sl@0: sl@0: return $msg sl@0: } sl@0: sl@0: }