📄 safe.tcl
字号:
AliasSubset $slave file file dir.* join root.* ext.* tail \ path.* split # This alias interposes on the 'exit' command and cleanly terminates # the slave. ::interp alias $slave exit {} [namespace current]::interpDelete $slave # The allowed slave variables already have been set # by Tcl_MakeSafe(3) # Source init.tcl into the slave, to get auto_load and other # procedures defined: # We don't try to use the -rsrc on the mac because it would get # confusing if you would want to customize init.tcl # for a given set of safe slaves, on all the platforms # you just need to give a specific access_path and # the mac should be no exception. As there is no # obvious full "safe ressources" design nor implementation # for the mac, safe interps there will just don't # have that ability. (A specific app can still reenable # that using custom aliases if they want to). # It would also make the security analysis and the Safe Tcl security # model platform dependant and thus more error prone. if {[catch {::interp eval $slave\ {source [file join $tcl_library init.tcl]}}\ msg]} { Log $slave "can't source init.tcl ($msg)"; error "can't source init.tcl into slave $slave ($msg)" } return $slave } # Add (only if needed, avoid duplicates) 1 level of # sub directories to an existing path list. # Also removes non directories from the returned list. proc AddSubDirs {pathList} { set res {} foreach dir $pathList { if {[file isdirectory $dir]} { # check that we don't have it yet as a children # of a previous dir if {[lsearch -exact $res $dir]<0} { lappend res $dir; } foreach sub [glob -nocomplain -- [file join $dir *]] { if { ([file isdirectory $sub]) && ([lsearch -exact $res $sub]<0) } { # new sub dir, add it ! lappend res $sub; } } } } return $res; } # This procedure deletes a safe slave managed by Safe Tcl and # cleans up associated state:proc ::safe::interpDelete {slave} { Log $slave "About to delete" NOTICE; # If the slave has a cleanup hook registered, call it. # check the existance because we might be called to delete an interp # which has not been registered with us at all set hookname [DeleteHookName $slave]; if {[Exists $hookname]} { set hook [Set $hookname]; if {![::tcl::Lempty $hook]} { # remove the hook now, otherwise if the hook # calls us somehow, we'll loop Unset $hookname; if {[catch {eval $hook [list $slave]} err]} { Log $slave "Delete hook error ($err)"; } } } # Discard the global array of state associated with the slave, and # delete the interpreter. set statename [InterpStateName $slave]; if {[Exists $statename]} { Unset $statename; } # if we have been called twice, the interp might have been deleted # already if {[::interp exists $slave]} { ::interp delete $slave; Log $slave "Deleted" NOTICE; } return } # Set (or get) the loging mecanism proc ::safe::setLogCmd {args} { variable Log; if {[llength $args] == 0} { return $Log; } else { if {[llength $args] == 1} { set Log [lindex $args 0]; } else { set Log $args } }} # internal variable variable Log {} # ------------------- END OF PUBLIC METHODS ------------ # # sets the slave auto_path to the master recorded value. # also sets tcl_library to the first token of the virtual path. # proc SyncAccessPath {slave} { set slave_auto_path [Set [VirtualPathListName $slave]]; ::interp eval $slave [list set auto_path $slave_auto_path]; Log $slave \ "auto_path in $slave has been set to $slave_auto_path"\ NOTICE; ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]]; } # base name for storing all the slave states # the array variable name for slave foo is thus "Sfoo" # and for sub slave {foo bar} "Sfoo bar" (spaces are handled # ok everywhere (or should)) # We add the S prefix to avoid that a slave interp called "Log" # would smash our "Log" variable. proc InterpStateName {slave} { return "S$slave"; } # Check that the given slave is "one of us" proc IsInterp {slave} { expr { ([Exists [InterpStateName $slave]]) && ([::interp exists $slave])} } # returns the virtual token for directory number N # if the slave argument is given, # it will return the corresponding master global variable name proc PathToken {n {slave ""}} { if {[string compare "" $slave]} { return "[InterpStateName $slave](access_path,$n)"; } else { # We need to have a ":" in the token string so # [file join] on the mac won't turn it into a relative # path. return "p(:$n:)"; } } # returns the variable name of the complete path list proc PathListName {slave} { return "[InterpStateName $slave](access_path)"; } # returns the variable name of the complete path list proc VirtualPathListName {slave} { return "[InterpStateName $slave](access_path_slave)"; } # returns the variable name of the number of items proc PathNumberName {slave} { return "[InterpStateName $slave](access_path,n)"; } # returns the staticsok flag var name proc StaticsOkName {slave} { return "[InterpStateName $slave](staticsok)"; } # returns the nestedok flag var name proc NestedOkName {slave} { return "[InterpStateName $slave](nestedok)"; } # Run some code at the namespace toplevel proc Toplevel {args} { namespace eval [namespace current] $args; } # set/get values proc Set {args} { eval Toplevel set $args; } # lappend on toplevel vars proc Lappend {args} { eval Toplevel lappend $args; } # unset a var/token (currently just an global level eval) proc Unset {args} { eval Toplevel unset $args; } # test existance proc Exists {varname} { Toplevel info exists $varname; } # short cut for access path getting proc GetAccessPath {slave} { Set [PathListName $slave] } # short cut for statics ok flag getting proc StaticsOk {slave} { Set [StaticsOkName $slave] } # short cut for getting the multiples interps sub loading ok flag proc NestedOk {slave} { Set [NestedOkName $slave] } # interp deletion storing hook name proc DeleteHookName {slave} { return [InterpStateName $slave](cleanupHook) } # # translate virtual path into real path # proc TranslatePath {slave path} { # somehow strip the namespaces 'functionality' out (the danger # is that we would strip valid macintosh "../" queries... : if {[regexp {(::)|(\.\.)} $path]} { error "invalid characters in path $path"; } set n [expr {[Set [PathNumberName $slave]]-1}]; for {} {$n>=0} {incr n -1} { # fill the token virtual names with their real value set [PathToken $n] [Set [PathToken $n $slave]]; } # replaces the token by their value subst -nobackslashes -nocommands $path; } # Log eventually log an error # to enable error logging, set Log to {puts stderr} for instance proc Log {slave msg {type ERROR}} { variable Log; if {[info exists Log] && [llength $Log]} { eval $Log [list "$type for slave $slave : $msg"]; } } # file name control (limit access to files/ressources that should be # a valid tcl source file) proc CheckFileName {slave file} { # limit what can be sourced to .tcl # and forbid files with more than 1 dot and # longer than 14 chars set ftail [file tail $file]; if {[string length $ftail]>14} { error "$ftail: filename too long"; } if {[regexp {\..*\.} $ftail]} { error "$ftail: more than one dot is forbidden"; } if {[string compare $ftail "tclIndex"] && \ [string compare [string tolower [file extension $ftail]]\ ".tcl"]} { error "$ftail: must be a *.tcl or tclIndex"; } if {![file exists $file]} { # don't tell the file path error "no such file or directory"; } if {![file readable $file]} { # don't tell the file path error "not readable"; } } # AliasSource is the target of the "source" alias in safe interpreters. proc AliasSource {slave args} { set argc [llength $args]; # Allow only "source filename" # (and not mac specific -rsrc for instance - see comment in ::init # for current rationale) if {$argc != 1} { set msg "wrong # args: should be \"source fileName\"" Log $slave "$msg ($args)"; return -code error $msg; } set file [lindex $args 0] # get the real path from the virtual one. if {[catch {set file [TranslatePath $slave $file]} msg]} { Log $slave $msg; return -code error "permission denied" } # check that the path is in the access path of that slave if {[catch {FileInAccessPath $slave $file} msg]} { Log $slave $msg; return -code error "permission denied" } # do the checks on the filename : if {[catch {CheckFileName $slave $file} msg]} { Log $slave "$file:$msg"; return -code error $msg; } # passed all the tests , lets source it: if {[catch {::interp invokehidden $slave source $file} msg]} { Log $slave $msg; return -code error "script error"; } return $msg } # AliasLoad is the target of the "load" alias in safe interpreters. proc AliasLoad {slave file args} { set argc [llength $args]; if {$argc > 2} { set msg "load error: too many arguments"; Log $slave "$msg ($argc) {$file $args}"; return -code error $msg; } # package name (can be empty if file is not). set package [lindex $args 0]; # Determine where to load. load use a relative interp path # and {} means self, so we can directly and safely use passed arg. set target [lindex $args 1]; if {[string length $target]} { # we will try to load into a sub sub interp # check that we want to authorize that. if {![NestedOk $slave]} { Log $slave "loading to a sub interp (nestedok)\ disabled (trying to load $package to $target)"; return -code error "permission denied (nested load)"; } } # Determine what kind of load is requested if {[string length $file] == 0} { # static package loading if {[string length $package] == 0} { set msg "load error: empty filename and no package name"; Log $slave $msg; return -code error $msg; } if {![StaticsOk $slave]} { Log $slave "static packages loading disabled\ (trying to load $package to $target)"; return -code error "permission denied (static package)"; } } else { # file loading # get the real path from the virtual one. if {[catch {set file [TranslatePath $slave $file]} msg]} { Log $slave $msg; return -code error "permission denied" } # check the translated path if {[catch {FileInAccessPath $slave $file} msg]} { Log $slave $msg; return -code error "permission denied (path)" } } if {[catch {::interp invokehidden\ $slave load $file $package $target} msg]} { Log $slave $msg; return -code error $msg } return $msg } # FileInAccessPath raises an error if the file is not found in # the list of directories contained in the (master side recorded) slave's # access path. # the security here relies on "file dirname" answering the proper # result.... needs checking ? proc FileInAccessPath {slave file} { set access_path [GetAccessPath $slave]; if {[file isdirectory $file]} { error "\"$file\": is a directory" } set parent [file dirname $file] if {[lsearch -exact $access_path $parent] == -1} { error "\"$file\": not in access_path"; } } # This procedure enables access from a safe interpreter to only a subset of # the subcommands of a command: proc Subset {slave command okpat args} { set subcommand [lindex $args 0] if {[regexp $okpat $subcommand]} { return [eval {$command $subcommand} [lrange $args 1 end]] } set msg "not allowed to invoke subcommand $subcommand of $command"; Log $slave $msg; error $msg; } # This procedure installs an alias in a slave that invokes "safesubset" # in the master to execute allowed subcommands. It precomputes the pattern # of allowed subcommands; you can use wildcards in the pattern if you wish # to allow subcommand abbreviation. # # Syntax is: AliasSubset slave alias target subcommand1 subcommand2... proc AliasSubset {slave alias target args} { set pat ^(; set sep "" foreach sub $args { append pat $sep$sub set sep | } append pat )\$ ::interp alias $slave $alias {}\ [namespace current]::Subset $slave $target $pat }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -