safe.tcl
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:27k
源码类别:

通讯编程

开发平台:

Visual C++

  1. # safe.tcl --
  2. #
  3. # This file provide a safe loading/sourcing mechanism for safe interpreters.
  4. # It implements a virtual path mecanism to hide the real pathnames from the
  5. # slave. It runs in a master interpreter and sets up data structure and
  6. # aliases that will be invoked when used from a slave interpreter.
  7. # See the safe.n man page for details.
  8. #
  9. # Copyright (c) 1996-1997 Sun Microsystems, Inc.
  10. #
  11. # See the file "license.terms" for information on usage and redistribution
  12. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  13. #
  14. # RCS: @(#) $Id: safe.tcl,v 1.9.2.3 2005/07/22 21:59:41 dgp Exp $
  15. #
  16. # The implementation is based on namespaces. These naming conventions
  17. # are followed:
  18. # Private procs starts with uppercase.
  19. # Public  procs are exported and starts with lowercase
  20. #
  21. # Needed utilities package
  22. package require opt 0.4.1;
  23. # Create the safe namespace
  24. namespace eval ::safe {
  25.     # Exported API:
  26.     namespace export interpCreate interpInit interpConfigure interpDelete 
  27.     interpAddToAccessPath interpFindInAccessPath setLogCmd
  28.     ####
  29.     #
  30.     # Setup the arguments parsing
  31.     #
  32.     ####
  33.     # Make sure that our temporary variable is local to this
  34.     # namespace.  [Bug 981733]
  35.     variable temp
  36.     # Share the descriptions
  37.     set temp [::tcl::OptKeyRegister {
  38. {-accessPath -list {} "access path for the slave"}
  39. {-noStatics "prevent loading of statically linked pkgs"}
  40. {-statics true "loading of statically linked pkgs"}
  41. {-nestedLoadOk "allow nested loading"}
  42. {-nested false "nested loading"}
  43. {-deleteHook -script {} "delete hook"}
  44.     }]
  45.     # create case (slave is optional)
  46.     ::tcl::OptKeyRegister {
  47. {?slave? -name {} "name of the slave (optional)"}
  48.     } ::safe::interpCreate
  49.     # adding the flags sub programs to the command program
  50.     # (relying on Opt's internal implementation details)
  51.     lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)
  52.     # init and configure (slave is needed)
  53.     ::tcl::OptKeyRegister {
  54. {slave -name {} "name of the slave"}
  55.     } ::safe::interpIC
  56.     # adding the flags sub programs to the command program
  57.     # (relying on Opt's internal implementation details)
  58.     lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)
  59.     # temp not needed anymore
  60.     ::tcl::OptKeyDelete $temp
  61.     # Helper function to resolve the dual way of specifying staticsok
  62.     # (either by -noStatics or -statics 0)
  63.     proc InterpStatics {} {
  64. foreach v {Args statics noStatics} {
  65.     upvar $v $v
  66. }
  67. set flag [::tcl::OptProcArgGiven -noStatics];
  68. if {$flag && (!$noStatics == !$statics) 
  69.           && ([::tcl::OptProcArgGiven -statics])} {
  70.     return -code error
  71.     "conflicting values given for -statics and -noStatics"
  72. }
  73. if {$flag} {
  74.     return [expr {!$noStatics}]
  75. } else {
  76.     return $statics
  77. }
  78.     }
  79.     # Helper function to resolve the dual way of specifying nested loading
  80.     # (either by -nestedLoadOk or -nested 1)
  81.     proc InterpNested {} {
  82. foreach v {Args nested nestedLoadOk} {
  83.     upvar $v $v
  84. }
  85. set flag [::tcl::OptProcArgGiven -nestedLoadOk];
  86. # note that the test here is the opposite of the "InterpStatics"
  87. # one (it is not -noNested... because of the wanted default value)
  88. if {$flag && (!$nestedLoadOk != !$nested) 
  89.           && ([::tcl::OptProcArgGiven -nested])} {
  90.     return -code error
  91.     "conflicting values given for -nested and -nestedLoadOk"
  92. }
  93. if {$flag} {
  94.     # another difference with "InterpStatics"
  95.     return $nestedLoadOk
  96. } else {
  97.     return $nested
  98. }
  99.     }
  100.     ####
  101.     #
  102.     #  API entry points that needs argument parsing :
  103.     #
  104.     ####
  105.     # Interface/entry point function and front end for "Create"
  106.     proc interpCreate {args} {
  107. set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
  108. InterpCreate $slave $accessPath 
  109. [InterpStatics] [InterpNested] $deleteHook
  110.     }
  111.     proc interpInit {args} {
  112. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  113. if {![::interp exists $slave]} {
  114.     return -code error ""$slave" is not an interpreter"
  115. }
  116. InterpInit $slave $accessPath 
  117. [InterpStatics] [InterpNested] $deleteHook;
  118.     }
  119.     proc CheckInterp {slave} {
  120. if {![IsInterp $slave]} {
  121.     return -code error 
  122.     ""$slave" is not an interpreter managed by ::safe::"
  123. }
  124.     }
  125.     # Interface/entry point function and front end for "Configure"
  126.     # This code is awfully pedestrian because it would need
  127.     # more coupling and support between the way we store the
  128.     # configuration values in safe::interp's and the Opt package
  129.     # Obviously we would like an OptConfigure
  130.     # to avoid duplicating all this code everywhere. -> TODO
  131.     # (the app should share or access easily the program/value
  132.     #  stored by opt)
  133.     # This is even more complicated by the boolean flags with no values
  134.     # that we had the bad idea to support for the sake of user simplicity
  135.     # in create/init but which makes life hard in configure...
  136.     # So this will be hopefully written and some integrated with opt1.0
  137.     # (hopefully for tcl8.1 ?)
  138.     proc interpConfigure {args} {
  139. switch [llength $args] {
  140.     1 {
  141. # If we have exactly 1 argument
  142. # the semantic is to return all the current configuration
  143. # We still call OptKeyParse though we know that "slave"
  144. # is our given argument because it also checks
  145. # for the "-help" option.
  146. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  147. CheckInterp $slave
  148. set res {}
  149. lappend res [list -accessPath [Set [PathListName $slave]]]
  150. lappend res [list -statics    [Set [StaticsOkName $slave]]]
  151. lappend res [list -nested     [Set [NestedOkName $slave]]]
  152. lappend res [list -deleteHook [Set [DeleteHookName $slave]]]
  153. join $res
  154.     }
  155.     2 {
  156. # If we have exactly 2 arguments
  157. # the semantic is a "configure get"
  158. ::tcl::Lassign $args slave arg
  159. # get the flag sub program (we 'know' about Opt's internal
  160. # representation of data)
  161. set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]
  162. set hits [::tcl::OptHits desc $arg]
  163.                 if {$hits > 1} {
  164.                     return -code error [::tcl::OptAmbigous $desc $arg]
  165.                 } elseif {$hits == 0} {
  166.                     return -code error [::tcl::OptFlagUsage $desc $arg]
  167.                 }
  168. CheckInterp $slave
  169. set item [::tcl::OptCurDesc $desc]
  170. set name [::tcl::OptName $item]
  171. switch -exact -- $name {
  172.     -accessPath {
  173. return [list -accessPath [Set [PathListName $slave]]]
  174.     }
  175.     -statics {
  176. return [list -statics    [Set [StaticsOkName $slave]]]
  177.     }
  178.     -nested {
  179. return [list -nested     [Set [NestedOkName $slave]]]
  180.     }
  181.     -deleteHook {
  182. return [list -deleteHook [Set [DeleteHookName $slave]]]
  183.     }
  184.     -noStatics {
  185. # it is most probably a set in fact
  186. # but we would need then to jump to the set part
  187. # and it is not *sure* that it is a set action
  188. # that the user want, so force it to use the
  189. # unambigous -statics ?value? instead:
  190. return -code error
  191. "ambigous query (get or set -noStatics ?)
  192. use -statics instead"
  193.     }
  194.     -nestedLoadOk {
  195. return -code error
  196. "ambigous query (get or set -nestedLoadOk ?)
  197. use -nested instead"
  198.     }
  199.     default {
  200. return -code error "unknown flag $name (bug)"
  201.     }
  202. }
  203.     }
  204.     default {
  205. # Otherwise we want to parse the arguments like init and create
  206. # did
  207. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  208. CheckInterp $slave
  209. # Get the current (and not the default) values of
  210. # whatever has not been given:
  211. if {![::tcl::OptProcArgGiven -accessPath]} {
  212.     set doreset 1
  213.     set accessPath [Set [PathListName $slave]]
  214. } else {
  215.     set doreset 0
  216. }
  217. if {(![::tcl::OptProcArgGiven -statics]) 
  218. && (![::tcl::OptProcArgGiven -noStatics]) } {
  219.     set statics    [Set [StaticsOkName $slave]]
  220. } else {
  221.     set statics    [InterpStatics]
  222. }
  223. if {([::tcl::OptProcArgGiven -nested]) 
  224. || ([::tcl::OptProcArgGiven -nestedLoadOk]) } {
  225.     set nested     [InterpNested]
  226. } else {
  227.     set nested     [Set [NestedOkName $slave]]
  228. }
  229. if {![::tcl::OptProcArgGiven -deleteHook]} {
  230.     set deleteHook [Set [DeleteHookName $slave]]
  231. }
  232. # we can now reconfigure :
  233. InterpSetConfig $slave $accessPath $statics $nested $deleteHook
  234. # auto_reset the slave (to completly synch the new access_path)
  235. if {$doreset} {
  236.     if {[catch {::interp eval $slave {auto_reset}} msg]} {
  237. Log $slave "auto_reset failed: $msg"
  238.     } else {
  239. Log $slave "successful auto_reset" NOTICE
  240.     }
  241. }
  242.     }
  243. }
  244.     }
  245.     ####
  246.     #
  247.     #  Functions that actually implements the exported APIs
  248.     #
  249.     ####
  250.     #
  251.     # safe::InterpCreate : doing the real job
  252.     #
  253.     # This procedure creates a safe slave and initializes it with the
  254.     # safe base aliases.
  255.     # NB: slave name must be simple alphanumeric string, no spaces,
  256.     # no (), no {},...  {because the state array is stored as part of the name}
  257.     #
  258.     # Returns the slave name.
  259.     #
  260.     # Optional Arguments : 
  261.     # + slave name : if empty, generated name will be used
  262.     # + access_path: path list controlling where load/source can occur,
  263.     #                if empty: the master auto_path will be used.
  264.     # + staticsok  : flag, if 0 :no static package can be loaded (load {} Xxx)
  265.     #                      if 1 :static packages are ok.
  266.     # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
  267.     #                      if 1 : multiple levels are ok.
  268.     
  269.     # use the full name and no indent so auto_mkIndex can find us
  270.     proc ::safe::InterpCreate {
  271. slave 
  272. access_path
  273. staticsok
  274. nestedok
  275. deletehook
  276.     } {
  277. # Create the slave.
  278. if {$slave ne ""} {
  279.     ::interp create -safe $slave
  280. } else {
  281.     # empty argument: generate slave name
  282.     set slave [::interp create -safe]
  283. }
  284. Log $slave "Created" NOTICE
  285. # Initialize it. (returns slave name)
  286. InterpInit $slave $access_path $staticsok $nestedok $deletehook
  287.     }
  288.     #
  289.     # InterpSetConfig (was setAccessPath) :
  290.     #    Sets up slave virtual auto_path and corresponding structure
  291.     #    within the master. Also sets the tcl_library in the slave
  292.     #    to be the first directory in the path.
  293.     #    Nb: If you change the path after the slave has been initialized
  294.     #    you probably need to call "auto_reset" in the slave in order that it
  295.     #    gets the right auto_index() array values.
  296.     proc ::safe::InterpSetConfig {slave access_path staticsok
  297.     nestedok deletehook} {
  298. # determine and store the access path if empty
  299. if {$access_path eq ""} {
  300.     set access_path [uplevel #0 set auto_path]
  301.     # Make sure that tcl_library is in auto_path
  302.     # and at the first position (needed by setAccessPath)
  303.     set where [lsearch -exact $access_path [info library]]
  304.     if {$where == -1} {
  305. # not found, add it.
  306. set access_path [concat [list [info library]] $access_path]
  307. Log $slave "tcl_library was not in auto_path,
  308. added it to slave's access_path" NOTICE
  309.     } elseif {$where != 0} {
  310. # not first, move it first
  311. set access_path [concat [list [info library]]
  312. [lreplace $access_path $where $where]]
  313. Log $slave "tcl_libray was not in first in auto_path,
  314. moved it to front of slave's access_path" NOTICE
  315.     
  316.     }
  317.     # Add 1st level sub dirs (will searched by auto loading from tcl
  318.     # code in the slave using glob and thus fail, so we add them
  319.     # here so by default it works the same).
  320.     set access_path [AddSubDirs $access_path]
  321. }
  322. Log $slave "Setting accessPath=($access_path) staticsok=$staticsok
  323. nestedok=$nestedok deletehook=($deletehook)" NOTICE
  324. # clear old autopath if it existed
  325. set nname [PathNumberName $slave]
  326. if {[Exists $nname]} {
  327.     set n [Set $nname]
  328.     for {set i 0} {$i<$n} {incr i} {
  329. Unset [PathToken $i $slave]
  330.     }
  331. }
  332. # build new one
  333. set slave_auto_path {}
  334. set i 0
  335. foreach dir $access_path {
  336.     Set [PathToken $i $slave] $dir
  337.     lappend slave_auto_path "$[PathToken $i]"
  338.     incr i
  339. }
  340. Set $nname $i
  341. Set [PathListName $slave] $access_path
  342. Set [VirtualPathListName $slave] $slave_auto_path
  343. Set [StaticsOkName $slave] $staticsok
  344. Set [NestedOkName $slave] $nestedok
  345. Set [DeleteHookName $slave] $deletehook
  346. SyncAccessPath $slave
  347.     }
  348.     #
  349.     #
  350.     # FindInAccessPath:
  351.     #    Search for a real directory and returns its virtual Id
  352.     #    (including the "$")
  353. proc ::safe::interpFindInAccessPath {slave path} {
  354. set access_path [GetAccessPath $slave]
  355. set where [lsearch -exact $access_path $path]
  356. if {$where == -1} {
  357.     return -code error "$path not found in access path $access_path"
  358. }
  359. return "$[PathToken $where]"
  360.     }
  361.     #
  362.     # addToAccessPath:
  363.     #    add (if needed) a real directory to access path
  364.     #    and return its virtual token (including the "$").
  365. proc ::safe::interpAddToAccessPath {slave path} {
  366. # first check if the directory is already in there
  367. if {![catch {interpFindInAccessPath $slave $path} res]} {
  368.     return $res
  369. }
  370. # new one, add it:
  371. set nname [PathNumberName $slave]
  372. set n [Set $nname]
  373. Set [PathToken $n $slave] $path
  374. set token "$[PathToken $n]"
  375. Lappend [VirtualPathListName $slave] $token
  376. Lappend [PathListName $slave] $path
  377. Set $nname [expr {$n+1}]
  378. SyncAccessPath $slave
  379. return $token
  380.     }
  381.     # This procedure applies the initializations to an already existing
  382.     # interpreter. It is useful when you want to install the safe base
  383.     # aliases into a preexisting safe interpreter.
  384.     proc ::safe::InterpInit {
  385. slave 
  386. access_path
  387. staticsok
  388. nestedok
  389. deletehook
  390.     } {
  391. # Configure will generate an access_path when access_path is
  392. # empty.
  393. InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook
  394. # These aliases let the slave load files to define new commands
  395. # NB we need to add [namespace current], aliases are always
  396. # absolute paths.
  397. ::interp alias $slave source {} [namespace current]::AliasSource $slave
  398. ::interp alias $slave load {} [namespace current]::AliasLoad $slave
  399. # This alias lets the slave use the encoding names, convertfrom,
  400. # convertto, and system, but not "encoding system <name>" to set
  401. # the system encoding.
  402. ::interp alias $slave encoding {} [namespace current]::AliasEncoding 
  403. $slave
  404. # This alias lets the slave have access to a subset of the 'file'
  405. # command functionality.
  406. AliasSubset $slave file file dir.* join root.* ext.* tail 
  407. path.* split
  408. # This alias interposes on the 'exit' command and cleanly terminates
  409. # the slave.
  410. ::interp alias $slave exit {} [namespace current]::interpDelete $slave
  411. # The allowed slave variables already have been set
  412. # by Tcl_MakeSafe(3)
  413. # Source init.tcl into the slave, to get auto_load and other
  414. # procedures defined:
  415. # We don't try to use the -rsrc on the mac because it would get
  416. # confusing if you would want to customize init.tcl
  417. # for a given set of safe slaves, on all the platforms
  418. # you just need to give a specific access_path and
  419. # the mac should be no exception. As there is no
  420. # obvious full "safe ressources" design nor implementation
  421. # for the mac, safe interps there will just don't
  422. # have that ability. (A specific app can still reenable
  423. # that using custom aliases if they want to).
  424. # It would also make the security analysis and the Safe Tcl security
  425. # model platform dependant and thus more error prone.
  426. if {[catch {::interp eval $slave
  427. {source [file join $tcl_library init.tcl]}} msg]} {
  428.     Log $slave "can't source init.tcl ($msg)"
  429.     error "can't source init.tcl into slave $slave ($msg)"
  430. }
  431. return $slave
  432.     }
  433.     # Add (only if needed, avoid duplicates) 1 level of
  434.     # sub directories to an existing path list.
  435.     # Also removes non directories from the returned list.
  436.     proc AddSubDirs {pathList} {
  437. set res {}
  438. foreach dir $pathList {
  439.     if {[file isdirectory $dir]} {
  440. # check that we don't have it yet as a children
  441. # of a previous dir
  442. if {[lsearch -exact $res $dir]<0} {
  443.     lappend res $dir
  444. }
  445. foreach sub [glob -directory $dir -nocomplain *] {
  446.     if {([file isdirectory $sub]) 
  447.     && ([lsearch -exact $res $sub]<0) } {
  448. # new sub dir, add it !
  449.                 lappend res $sub
  450.             }
  451. }
  452.     }
  453. }
  454. return $res
  455.     }
  456.     # This procedure deletes a safe slave managed by Safe Tcl and
  457.     # cleans up associated state:
  458. proc ::safe::interpDelete {slave} {
  459.         Log $slave "About to delete" NOTICE
  460. # If the slave has a cleanup hook registered, call it.
  461. # check the existance because we might be called to delete an interp
  462. # which has not been registered with us at all
  463. set hookname [DeleteHookName $slave]
  464. if {[Exists $hookname]} {
  465.     set hook [Set $hookname]
  466.     if {![::tcl::Lempty $hook]} {
  467. # remove the hook now, otherwise if the hook
  468. # calls us somehow, we'll loop
  469. Unset $hookname
  470. if {[catch {eval $hook [list $slave]} err]} {
  471.     Log $slave "Delete hook error ($err)"
  472. }
  473.     }
  474. }
  475. # Discard the global array of state associated with the slave, and
  476. # delete the interpreter.
  477. set statename [InterpStateName $slave]
  478. if {[Exists $statename]} {
  479.     Unset $statename
  480. }
  481. # if we have been called twice, the interp might have been deleted
  482. # already
  483. if {[::interp exists $slave]} {
  484.     ::interp delete $slave
  485.     Log $slave "Deleted" NOTICE
  486. }
  487. return
  488.     }
  489.     # Set (or get) the loging mecanism 
  490. proc ::safe::setLogCmd {args} {
  491.     variable Log
  492.     if {[llength $args] == 0} {
  493. return $Log
  494.     } else {
  495. if {[llength $args] == 1} {
  496.     set Log [lindex $args 0]
  497. } else {
  498.     set Log $args
  499. }
  500.     }
  501. }
  502.     # internal variable
  503.     variable Log {}
  504.     # ------------------- END OF PUBLIC METHODS ------------
  505.     #
  506.     # sets the slave auto_path to the master recorded value.
  507.     # also sets tcl_library to the first token of the virtual path.
  508.     #
  509.     proc SyncAccessPath {slave} {
  510. set slave_auto_path [Set [VirtualPathListName $slave]]
  511. ::interp eval $slave [list set auto_path $slave_auto_path]
  512. Log $slave "auto_path in $slave has been set to $slave_auto_path"
  513. NOTICE
  514. ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]]
  515.     }
  516.     # base name for storing all the slave states
  517.     # the array variable name for slave foo is thus "Sfoo"
  518.     # and for sub slave {foo bar} "Sfoo bar" (spaces are handled
  519.     # ok everywhere (or should))
  520.     # We add the S prefix to avoid that a slave interp called "Log"
  521.     # would smash our "Log" variable.
  522.     proc InterpStateName {slave} {
  523. return "S$slave"
  524.     }
  525.     # Check that the given slave is "one of us"
  526.     proc IsInterp {slave} {
  527. expr {[Exists [InterpStateName $slave]] && [::interp exists $slave]}
  528.     }
  529.     # returns the virtual token for directory number N
  530.     # if the slave argument is given, 
  531.     # it will return the corresponding master global variable name
  532.     proc PathToken {n {slave ""}} {
  533. if {$slave ne ""} {
  534.     return "[InterpStateName $slave](access_path,$n)"
  535. } else {
  536.     # We need to have a ":" in the token string so
  537.     # [file join] on the mac won't turn it into a relative
  538.     # path.
  539.     return "p(:$n:)"
  540. }
  541.     }
  542.     # returns the variable name of the complete path list
  543.     proc PathListName {slave} {
  544. return "[InterpStateName $slave](access_path)"
  545.     }
  546.     # returns the variable name of the complete path list
  547.     proc VirtualPathListName {slave} {
  548. return "[InterpStateName $slave](access_path_slave)"
  549.     }
  550.     # returns the variable name of the number of items
  551.     proc PathNumberName {slave} {
  552. return "[InterpStateName $slave](access_path,n)"
  553.     }
  554.     # returns the staticsok flag var name
  555.     proc StaticsOkName {slave} {
  556. return "[InterpStateName $slave](staticsok)"
  557.     }
  558.     # returns the nestedok flag var name
  559.     proc NestedOkName {slave} {
  560. return "[InterpStateName $slave](nestedok)"
  561.     }
  562.     # Run some code at the namespace toplevel
  563.     proc Toplevel {args} {
  564. namespace eval [namespace current] $args
  565.     }
  566.     # set/get values
  567.     proc Set {args} {
  568. eval [linsert $args 0 Toplevel set]
  569.     }
  570.     # lappend on toplevel vars
  571.     proc Lappend {args} {
  572. eval [linsert $args 0 Toplevel lappend]
  573.     }
  574.     # unset a var/token (currently just an global level eval)
  575.     proc Unset {args} {
  576. eval [linsert $args 0 Toplevel unset]
  577.     }
  578.     # test existance 
  579.     proc Exists {varname} {
  580. Toplevel info exists $varname
  581.     }
  582.     # short cut for access path getting
  583.     proc GetAccessPath {slave} {
  584. Set [PathListName $slave]
  585.     }
  586.     # short cut for statics ok flag getting
  587.     proc StaticsOk {slave} {
  588. Set [StaticsOkName $slave]
  589.     }
  590.     # short cut for getting the multiples interps sub loading ok flag
  591.     proc NestedOk {slave} {
  592. Set [NestedOkName $slave]
  593.     }
  594.     # interp deletion storing hook name
  595.     proc DeleteHookName {slave} {
  596. return [InterpStateName $slave](cleanupHook)
  597.     }
  598.     #
  599.     # translate virtual path into real path
  600.     #
  601.     proc TranslatePath {slave path} {
  602. # somehow strip the namespaces 'functionality' out (the danger
  603. # is that we would strip valid macintosh "../" queries... :
  604. if {[regexp {(::)|(..)} $path]} {
  605.     error "invalid characters in path $path"
  606. }
  607. set n [expr {[Set [PathNumberName $slave]]-1}]
  608. for {} {$n>=0} {incr n -1} {
  609.     # fill the token virtual names with their real value
  610.     set [PathToken $n] [Set [PathToken $n $slave]]
  611. }
  612. # replaces the token by their value
  613. subst -nobackslashes -nocommands $path
  614.     }
  615.     # Log eventually log an error
  616.     # to enable error logging, set Log to {puts stderr} for instance
  617.     proc Log {slave msg {type ERROR}} {
  618. variable Log
  619. if {[info exists Log] && [llength $Log]} {
  620.     eval $Log [list "$type for slave $slave : $msg"]
  621. }
  622.     }
  623.     # file name control (limit access to files/ressources that should be
  624.     # a valid tcl source file)
  625.     proc CheckFileName {slave file} {
  626. # This used to limit what can be sourced to ".tcl" and forbid files
  627. # with more than 1 dot and longer than 14 chars, but I changed that
  628. # for 8.4 as a safe interp has enough internal protection already
  629. # to allow sourcing anything. - hobbs
  630. if {![file exists $file]} {
  631.     # don't tell the file path
  632.     error "no such file or directory"
  633. }
  634. if {![file readable $file]} {
  635.     # don't tell the file path
  636.     error "not readable"
  637. }
  638.     }
  639.     # AliasSource is the target of the "source" alias in safe interpreters.
  640.     proc AliasSource {slave args} {
  641. set argc [llength $args]
  642. # Allow only "source filename"
  643. # (and not mac specific -rsrc for instance - see comment in ::init
  644. # for current rationale)
  645. if {$argc != 1} {
  646.     set msg "wrong # args: should be "source fileName""
  647.     Log $slave "$msg ($args)"
  648.     return -code error $msg
  649. }
  650. set file [lindex $args 0]
  651. # get the real path from the virtual one.
  652. if {[catch {set file [TranslatePath $slave $file]} msg]} {
  653.     Log $slave $msg
  654.     return -code error "permission denied"
  655. }
  656. # check that the path is in the access path of that slave
  657. if {[catch {FileInAccessPath $slave $file} msg]} {
  658.     Log $slave $msg
  659.     return -code error "permission denied"
  660. }
  661. # do the checks on the filename :
  662. if {[catch {CheckFileName $slave $file} msg]} {
  663.     Log $slave "$file:$msg"
  664.     return -code error $msg
  665. }
  666. # passed all the tests , lets source it:
  667. if {[catch {::interp invokehidden $slave source $file} msg]} {
  668.     Log $slave $msg
  669.     return -code error "script error"
  670. }
  671. return $msg
  672.     }
  673.     # AliasLoad is the target of the "load" alias in safe interpreters.
  674.     proc AliasLoad {slave file args} {
  675. set argc [llength $args]
  676. if {$argc > 2} {
  677.     set msg "load error: too many arguments"
  678.     Log $slave "$msg ($argc) {$file $args}"
  679.     return -code error $msg
  680. }
  681. # package name (can be empty if file is not).
  682. set package [lindex $args 0]
  683. # Determine where to load. load use a relative interp path
  684. # and {} means self, so we can directly and safely use passed arg.
  685. set target [lindex $args 1]
  686. if {$target ne ""} {
  687.     # we will try to load into a sub sub interp
  688.     # check that we want to authorize that.
  689.     if {![NestedOk $slave]} {
  690. Log $slave "loading to a sub interp (nestedok)
  691. disabled (trying to load $package to $target)"
  692. return -code error "permission denied (nested load)"
  693.     }
  694.     
  695. }
  696. # Determine what kind of load is requested
  697. if {$file eq ""} {
  698.     # static package loading
  699.     if {$package eq ""} {
  700. set msg "load error: empty filename and no package name"
  701. Log $slave $msg
  702. return -code error $msg
  703.     }
  704.     if {![StaticsOk $slave]} {
  705. Log $slave "static packages loading disabled
  706. (trying to load $package to $target)"
  707. return -code error "permission denied (static package)"
  708.     }
  709. } else {
  710.     # file loading
  711.     # get the real path from the virtual one.
  712.     if {[catch {set file [TranslatePath $slave $file]} msg]} {
  713. Log $slave $msg
  714. return -code error "permission denied"
  715.     }
  716.     # check the translated path
  717.     if {[catch {FileInAccessPath $slave $file} msg]} {
  718. Log $slave $msg
  719. return -code error "permission denied (path)"
  720.     }
  721. }
  722. if {[catch {::interp invokehidden
  723. $slave load $file $package $target} msg]} {
  724.     Log $slave $msg
  725.     return -code error $msg
  726. }
  727. return $msg
  728.     }
  729.     # FileInAccessPath raises an error if the file is not found in
  730.     # the list of directories contained in the (master side recorded) slave's
  731.     # access path.
  732.     # the security here relies on "file dirname" answering the proper
  733.     # result.... needs checking ?
  734.     proc FileInAccessPath {slave file} {
  735. set access_path [GetAccessPath $slave]
  736. if {[file isdirectory $file]} {
  737.     error ""$file": is a directory"
  738. }
  739. set parent [file dirname $file]
  740. # Normalize paths for comparison since lsearch knows nothing of
  741. # potential pathname anomalies.
  742. set norm_parent [file normalize $parent]
  743. foreach path $access_path {
  744.     lappend norm_access_path [file normalize $path]
  745. }
  746. if {[lsearch -exact $norm_access_path $norm_parent] == -1} {
  747.     error ""$file": not in access_path"
  748. }
  749.     }
  750.     # This procedure enables access from a safe interpreter to only a subset of
  751.     # the subcommands of a command:
  752.     proc Subset {slave command okpat args} {
  753. set subcommand [lindex $args 0]
  754. if {[regexp $okpat $subcommand]} {
  755.     return [eval [linsert $args 0 $command]]
  756. }
  757. set msg "not allowed to invoke subcommand $subcommand of $command"
  758. Log $slave $msg
  759. error $msg
  760.     }
  761.     # This procedure installs an alias in a slave that invokes "safesubset"
  762.     # in the master to execute allowed subcommands. It precomputes the pattern
  763.     # of allowed subcommands; you can use wildcards in the pattern if you wish
  764.     # to allow subcommand abbreviation.
  765.     #
  766.     # Syntax is: AliasSubset slave alias target subcommand1 subcommand2...
  767.     proc AliasSubset {slave alias target args} {
  768. set pat ^(; set sep ""
  769. foreach sub $args {
  770.     append pat $sep$sub
  771.     set sep |
  772. }
  773. append pat )$
  774. ::interp alias $slave $alias {}
  775. [namespace current]::Subset $slave $target $pat
  776.     }
  777.     # AliasEncoding is the target of the "encoding" alias in safe interpreters.
  778.     proc AliasEncoding {slave args} {
  779. set argc [llength $args]
  780. set okpat "^(name.*|convert.*)$"
  781. set subcommand [lindex $args 0]
  782. if {[regexp $okpat $subcommand]} {
  783.     return [eval [linsert $args 0 
  784.     ::interp invokehidden $slave encoding]]
  785. }
  786. if {[string first $subcommand system] == 0} {
  787.     if {$argc == 1} {
  788. # passed all the tests , lets source it:
  789. if {[catch {::interp invokehidden 
  790. $slave encoding system} msg]} {
  791.     Log $slave $msg
  792.     return -code error "script error"
  793. }
  794.     } else {
  795. set msg "wrong # args: should be "encoding system""
  796. Log $slave $msg
  797. error $msg
  798.     }
  799. } else {
  800.     set msg "wrong # args: should be "encoding option ?arg ...?""
  801.     Log $slave $msg
  802.     error $msg
  803. }
  804. return $msg
  805.     }
  806. }