safe.tcl 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  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. #
  8. # See the safe.n man page for details.
  9. #
  10. # Copyright (c) 1996-1997 Sun Microsystems, Inc.
  11. #
  12. # See the file "license.terms" for information on usage and redistribution of
  13. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  14. #
  15. # The implementation is based on namespaces. These naming conventions are
  16. # followed:
  17. # Private procs starts with uppercase.
  18. # Public procs are exported and starts with lowercase
  19. #
  20. # Needed utilities package
  21. package require opt 0.4.1
  22. # Create the safe namespace
  23. namespace eval ::safe {
  24. # Exported API:
  25. namespace export interpCreate interpInit interpConfigure interpDelete \
  26. interpAddToAccessPath interpFindInAccessPath setLogCmd
  27. }
  28. # Helper function to resolve the dual way of specifying staticsok (either
  29. # by -noStatics or -statics 0)
  30. proc ::safe::InterpStatics {} {
  31. foreach v {Args statics noStatics} {
  32. upvar $v $v
  33. }
  34. set flag [::tcl::OptProcArgGiven -noStatics]
  35. if {$flag && (!$noStatics == !$statics)
  36. && ([::tcl::OptProcArgGiven -statics])} {
  37. return -code error\
  38. "conflicting values given for -statics and -noStatics"
  39. }
  40. if {$flag} {
  41. return [expr {!$noStatics}]
  42. } else {
  43. return $statics
  44. }
  45. }
  46. # Helper function to resolve the dual way of specifying nested loading
  47. # (either by -nestedLoadOk or -nested 1)
  48. proc ::safe::InterpNested {} {
  49. foreach v {Args nested nestedLoadOk} {
  50. upvar $v $v
  51. }
  52. set flag [::tcl::OptProcArgGiven -nestedLoadOk]
  53. # note that the test here is the opposite of the "InterpStatics" one
  54. # (it is not -noNested... because of the wanted default value)
  55. if {$flag && (!$nestedLoadOk != !$nested)
  56. && ([::tcl::OptProcArgGiven -nested])} {
  57. return -code error\
  58. "conflicting values given for -nested and -nestedLoadOk"
  59. }
  60. if {$flag} {
  61. # another difference with "InterpStatics"
  62. return $nestedLoadOk
  63. } else {
  64. return $nested
  65. }
  66. }
  67. ####
  68. #
  69. # API entry points that needs argument parsing :
  70. #
  71. ####
  72. # Interface/entry point function and front end for "Create"
  73. proc ::safe::interpCreate {args} {
  74. set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
  75. InterpCreate $slave $accessPath \
  76. [InterpStatics] [InterpNested] $deleteHook
  77. }
  78. proc ::safe::interpInit {args} {
  79. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  80. if {![::interp exists $slave]} {
  81. return -code error "\"$slave\" is not an interpreter"
  82. }
  83. InterpInit $slave $accessPath \
  84. [InterpStatics] [InterpNested] $deleteHook
  85. }
  86. # Check that the given slave is "one of us"
  87. proc ::safe::CheckInterp {slave} {
  88. namespace upvar ::safe S$slave state
  89. if {![info exists state] || ![::interp exists $slave]} {
  90. return -code error \
  91. "\"$slave\" is not an interpreter managed by ::safe::"
  92. }
  93. }
  94. # Interface/entry point function and front end for "Configure". This code
  95. # is awfully pedestrian because it would need more coupling and support
  96. # between the way we store the configuration values in safe::interp's and
  97. # the Opt package. Obviously we would like an OptConfigure to avoid
  98. # duplicating all this code everywhere.
  99. # -> TODO (the app should share or access easily the program/value stored
  100. # by opt)
  101. # This is even more complicated by the boolean flags with no values that
  102. # we had the bad idea to support for the sake of user simplicity in
  103. # create/init but which makes life hard in configure...
  104. # So this will be hopefully written and some integrated with opt1.0
  105. # (hopefully for tcl8.1 ?)
  106. proc ::safe::interpConfigure {args} {
  107. switch [llength $args] {
  108. 1 {
  109. # If we have exactly 1 argument the semantic is to return all
  110. # the current configuration. We still call OptKeyParse though
  111. # we know that "slave" is our given argument because it also
  112. # checks for the "-help" option.
  113. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  114. CheckInterp $slave
  115. namespace upvar ::safe S$slave state
  116. return [join [list \
  117. [list -accessPath $state(access_path)] \
  118. [list -statics $state(staticsok)] \
  119. [list -nested $state(nestedok)] \
  120. [list -deleteHook $state(cleanupHook)]]]
  121. }
  122. 2 {
  123. # If we have exactly 2 arguments the semantic is a "configure
  124. # get"
  125. lassign $args slave arg
  126. # get the flag sub program (we 'know' about Opt's internal
  127. # representation of data)
  128. set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]
  129. set hits [::tcl::OptHits desc $arg]
  130. if {$hits > 1} {
  131. return -code error [::tcl::OptAmbigous $desc $arg]
  132. } elseif {$hits == 0} {
  133. return -code error [::tcl::OptFlagUsage $desc $arg]
  134. }
  135. CheckInterp $slave
  136. namespace upvar ::safe S$slave state
  137. set item [::tcl::OptCurDesc $desc]
  138. set name [::tcl::OptName $item]
  139. switch -exact -- $name {
  140. -accessPath {return [list -accessPath $state(access_path)]}
  141. -statics {return [list -statics $state(staticsok)]}
  142. -nested {return [list -nested $state(nestedok)]}
  143. -deleteHook {return [list -deleteHook $state(cleanupHook)]}
  144. -noStatics {
  145. # it is most probably a set in fact but we would need
  146. # then to jump to the set part and it is not *sure*
  147. # that it is a set action that the user want, so force
  148. # it to use the unambigous -statics ?value? instead:
  149. return -code error\
  150. "ambigous query (get or set -noStatics ?)\
  151. use -statics instead"
  152. }
  153. -nestedLoadOk {
  154. return -code error\
  155. "ambigous query (get or set -nestedLoadOk ?)\
  156. use -nested instead"
  157. }
  158. default {
  159. return -code error "unknown flag $name (bug)"
  160. }
  161. }
  162. }
  163. default {
  164. # Otherwise we want to parse the arguments like init and
  165. # create did
  166. set Args [::tcl::OptKeyParse ::safe::interpIC $args]
  167. CheckInterp $slave
  168. namespace upvar ::safe S$slave state
  169. # Get the current (and not the default) values of whatever has
  170. # not been given:
  171. if {![::tcl::OptProcArgGiven -accessPath]} {
  172. set doreset 1
  173. set accessPath $state(access_path)
  174. } else {
  175. set doreset 0
  176. }
  177. if {
  178. ![::tcl::OptProcArgGiven -statics]
  179. && ![::tcl::OptProcArgGiven -noStatics]
  180. } {
  181. set statics $state(staticsok)
  182. } else {
  183. set statics [InterpStatics]
  184. }
  185. if {
  186. [::tcl::OptProcArgGiven -nested] ||
  187. [::tcl::OptProcArgGiven -nestedLoadOk]
  188. } {
  189. set nested [InterpNested]
  190. } else {
  191. set nested $state(nestedok)
  192. }
  193. if {![::tcl::OptProcArgGiven -deleteHook]} {
  194. set deleteHook $state(cleanupHook)
  195. }
  196. # we can now reconfigure :
  197. InterpSetConfig $slave $accessPath $statics $nested $deleteHook
  198. # auto_reset the slave (to completly synch the new access_path)
  199. if {$doreset} {
  200. if {[catch {::interp eval $slave {auto_reset}} msg]} {
  201. Log $slave "auto_reset failed: $msg"
  202. } else {
  203. Log $slave "successful auto_reset" NOTICE
  204. }
  205. }
  206. }
  207. }
  208. }
  209. ####
  210. #
  211. # Functions that actually implements the exported APIs
  212. #
  213. ####
  214. #
  215. # safe::InterpCreate : doing the real job
  216. #
  217. # This procedure creates a safe slave and initializes it with the safe
  218. # base aliases.
  219. # NB: slave name must be simple alphanumeric string, no spaces, no (), no
  220. # {},... {because the state array is stored as part of the name}
  221. #
  222. # Returns the slave name.
  223. #
  224. # Optional Arguments :
  225. # + slave name : if empty, generated name will be used
  226. # + access_path: path list controlling where load/source can occur,
  227. # if empty: the master auto_path will be used.
  228. # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)
  229. # if 1 :static packages are ok.
  230. # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
  231. # if 1 : multiple levels are ok.
  232. # use the full name and no indent so auto_mkIndex can find us
  233. proc ::safe::InterpCreate {
  234. slave
  235. access_path
  236. staticsok
  237. nestedok
  238. deletehook
  239. } {
  240. # Create the slave.
  241. if {$slave ne ""} {
  242. ::interp create -safe $slave
  243. } else {
  244. # empty argument: generate slave name
  245. set slave [::interp create -safe]
  246. }
  247. Log $slave "Created" NOTICE
  248. # Initialize it. (returns slave name)
  249. InterpInit $slave $access_path $staticsok $nestedok $deletehook
  250. }
  251. #
  252. # InterpSetConfig (was setAccessPath) :
  253. # Sets up slave virtual auto_path and corresponding structure within
  254. # the master. Also sets the tcl_library in the slave to be the first
  255. # directory in the path.
  256. # NB: If you change the path after the slave has been initialized you
  257. # probably need to call "auto_reset" in the slave in order that it gets
  258. # the right auto_index() array values.
  259. proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
  260. global auto_path
  261. # determine and store the access path if empty
  262. if {$access_path eq ""} {
  263. set access_path $auto_path
  264. # Make sure that tcl_library is in auto_path and at the first
  265. # position (needed by setAccessPath)
  266. set where [lsearch -exact $access_path [info library]]
  267. if {$where == -1} {
  268. # not found, add it.
  269. set access_path [linsert $access_path 0 [info library]]
  270. Log $slave "tcl_library was not in auto_path,\
  271. added it to slave's access_path" NOTICE
  272. } elseif {$where != 0} {
  273. # not first, move it first
  274. set access_path [linsert \
  275. [lreplace $access_path $where $where] \
  276. 0 [info library]]
  277. Log $slave "tcl_libray was not in first in auto_path,\
  278. moved it to front of slave's access_path" NOTICE
  279. }
  280. # Add 1st level sub dirs (will searched by auto loading from tcl
  281. # code in the slave using glob and thus fail, so we add them here
  282. # so by default it works the same).
  283. set access_path [AddSubDirs $access_path]
  284. }
  285. Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\
  286. nestedok=$nestedok deletehook=($deletehook)" NOTICE
  287. namespace upvar ::safe S$slave state
  288. # clear old autopath if it existed
  289. # build new one
  290. # Extend the access list with the paths used to look for Tcl Modules.
  291. # We save the virtual form separately as well, as syncing it with the
  292. # slave has to be defered until the necessary commands are present for
  293. # setup.
  294. set norm_access_path {}
  295. set slave_access_path {}
  296. set map_access_path {}
  297. set remap_access_path {}
  298. set slave_tm_path {}
  299. set i 0
  300. foreach dir $access_path {
  301. set token [PathToken $i]
  302. lappend slave_access_path $token
  303. lappend map_access_path $token $dir
  304. lappend remap_access_path $dir $token
  305. lappend norm_access_path [file normalize $dir]
  306. incr i
  307. }
  308. set morepaths [::tcl::tm::list]
  309. while {[llength $morepaths]} {
  310. set addpaths $morepaths
  311. set morepaths {}
  312. foreach dir $addpaths {
  313. # Prevent the addition of dirs on the tm list to the
  314. # result if they are already known.
  315. if {[dict exists $remap_access_path $dir]} {
  316. continue
  317. }
  318. set token [PathToken $i]
  319. lappend access_path $dir
  320. lappend slave_access_path $token
  321. lappend map_access_path $token $dir
  322. lappend remap_access_path $dir $token
  323. lappend norm_access_path [file normalize $dir]
  324. lappend slave_tm_path $token
  325. incr i
  326. # [Bug 2854929]
  327. # Recursively find deeper paths which may contain
  328. # modules. Required to handle modules with names like
  329. # 'platform::shell', which translate into
  330. # 'platform/shell-X.tm', i.e arbitrarily deep
  331. # subdirectories.
  332. lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
  333. }
  334. }
  335. set state(access_path) $access_path
  336. set state(access_path,map) $map_access_path
  337. set state(access_path,remap) $remap_access_path
  338. set state(access_path,norm) $norm_access_path
  339. set state(access_path,slave) $slave_access_path
  340. set state(tm_path_slave) $slave_tm_path
  341. set state(staticsok) $staticsok
  342. set state(nestedok) $nestedok
  343. set state(cleanupHook) $deletehook
  344. SyncAccessPath $slave
  345. }
  346. #
  347. #
  348. # FindInAccessPath:
  349. # Search for a real directory and returns its virtual Id (including the
  350. # "$")
  351. proc ::safe::interpFindInAccessPath {slave path} {
  352. namespace upvar ::safe S$slave state
  353. if {![dict exists $state(access_path,remap) $path]} {
  354. return -code error "$path not found in access path $access_path"
  355. }
  356. return [dict get $state(access_path,remap) $path]
  357. }
  358. #
  359. # addToAccessPath:
  360. # add (if needed) a real directory to access path and return its
  361. # virtual token (including the "$").
  362. proc ::safe::interpAddToAccessPath {slave path} {
  363. # first check if the directory is already in there
  364. # (inlined interpFindInAccessPath).
  365. namespace upvar ::safe S$slave state
  366. if {[dict exists $state(access_path,remap) $path]} {
  367. return [dict get $state(access_path,remap) $path]
  368. }
  369. # new one, add it:
  370. set token [PathToken [llength $state(access_path)]]
  371. lappend state(access_path) $path
  372. lappend state(access_path,slave) $token
  373. lappend state(access_path,map) $token $path
  374. lappend state(access_path,remap) $path $token
  375. lappend state(access_path,norm) [file normalize $path]
  376. SyncAccessPath $slave
  377. return $token
  378. }
  379. # This procedure applies the initializations to an already existing
  380. # interpreter. It is useful when you want to install the safe base aliases
  381. # into a preexisting safe interpreter.
  382. proc ::safe::InterpInit {
  383. slave
  384. access_path
  385. staticsok
  386. nestedok
  387. deletehook
  388. } {
  389. # Configure will generate an access_path when access_path is empty.
  390. InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook
  391. # NB we need to add [namespace current], aliases are always absolute
  392. # paths.
  393. # These aliases let the slave load files to define new commands
  394. # This alias lets the slave use the encoding names, convertfrom,
  395. # convertto, and system, but not "encoding system <name>" to set the
  396. # system encoding.
  397. # Handling Tcl Modules, we need a restricted form of Glob.
  398. # This alias interposes on the 'exit' command and cleanly terminates
  399. # the slave.
  400. foreach {command alias} {
  401. source AliasSource
  402. load AliasLoad
  403. encoding AliasEncoding
  404. exit interpDelete
  405. glob AliasGlob
  406. } {
  407. ::interp alias $slave $command {} [namespace current]::$alias $slave
  408. }
  409. # This alias lets the slave have access to a subset of the 'file'
  410. # command functionality.
  411. AliasSubset $slave file \
  412. file dir.* join root.* ext.* tail path.* split
  413. # Subcommands of info
  414. foreach {subcommand alias} {
  415. nameofexecutable AliasExeName
  416. } {
  417. ::interp alias $slave ::tcl::info::$subcommand \
  418. {} [namespace current]::$alias $slave
  419. }
  420. # The allowed slave variables already have been set by Tcl_MakeSafe(3)
  421. # Source init.tcl and tm.tcl into the slave, to get auto_load and
  422. # other procedures defined:
  423. if {[catch {::interp eval $slave {
  424. source [file join $tcl_library init.tcl]
  425. }} msg]} {
  426. Log $slave "can't source init.tcl ($msg)"
  427. return -code error "can't source init.tcl into slave $slave ($msg)"
  428. }
  429. if {[catch {::interp eval $slave {
  430. source [file join $tcl_library tm.tcl]
  431. }} msg]} {
  432. Log $slave "can't source tm.tcl ($msg)"
  433. return -code error "can't source tm.tcl into slave $slave ($msg)"
  434. }
  435. # Sync the paths used to search for Tcl modules. This can be done only
  436. # now, after tm.tcl was loaded.
  437. namespace upvar ::safe S$slave state
  438. if {[llength $state(tm_path_slave)] > 0} {
  439. ::interp eval $slave [list \
  440. ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
  441. }
  442. return $slave
  443. }
  444. # Add (only if needed, avoid duplicates) 1 level of sub directories to an
  445. # existing path list. Also removes non directories from the returned
  446. # list.
  447. proc ::safe::AddSubDirs {pathList} {
  448. set res {}
  449. foreach dir $pathList {
  450. if {[file isdirectory $dir]} {
  451. # check that we don't have it yet as a children of a previous
  452. # dir
  453. if {$dir ni $res} {
  454. lappend res $dir
  455. }
  456. foreach sub [glob -directory $dir -nocomplain *] {
  457. if {[file isdirectory $sub] && ($sub ni $res)} {
  458. # new sub dir, add it !
  459. lappend res $sub
  460. }
  461. }
  462. }
  463. }
  464. return $res
  465. }
  466. # This procedure deletes a safe slave managed by Safe Tcl and cleans up
  467. # associated state:
  468. proc ::safe::interpDelete {slave} {
  469. Log $slave "About to delete" NOTICE
  470. namespace upvar ::safe S$slave state
  471. # If the slave has a cleanup hook registered, call it. Check the
  472. # existance because we might be called to delete an interp which has
  473. # not been registered with us at all
  474. if {[info exists state(cleanupHook)]} {
  475. set hook $state(cleanupHook)
  476. if {[llength $hook]} {
  477. # remove the hook now, otherwise if the hook calls us somehow,
  478. # we'll loop
  479. unset state(cleanupHook)
  480. if {[catch {
  481. {*}$hook $slave
  482. } err]} {
  483. Log $slave "Delete hook error ($err)"
  484. }
  485. }
  486. }
  487. # Discard the global array of state associated with the slave, and
  488. # delete the interpreter.
  489. if {[info exists state]} {
  490. unset state
  491. }
  492. # if we have been called twice, the interp might have been deleted
  493. # already
  494. if {[::interp exists $slave]} {
  495. ::interp delete $slave
  496. Log $slave "Deleted" NOTICE
  497. }
  498. return
  499. }
  500. # Set (or get) the logging mecanism
  501. proc ::safe::setLogCmd {args} {
  502. variable Log
  503. set la [llength $args]
  504. if {$la == 0} {
  505. return $Log
  506. } elseif {$la == 1} {
  507. set Log [lindex $args 0]
  508. } else {
  509. set Log $args
  510. }
  511. if {$Log eq ""} {
  512. # Disable logging completely. Calls to it will be compiled out
  513. # of all users.
  514. proc ::safe::Log {args} {}
  515. } else {
  516. # Activate logging, define proper command.
  517. proc ::safe::Log {slave msg {type ERROR}} {
  518. variable Log
  519. {*}$Log "$type for slave $slave : $msg"
  520. return
  521. }
  522. }
  523. }
  524. # ------------------- END OF PUBLIC METHODS ------------
  525. #
  526. # Sets the slave auto_path to the master recorded value. Also sets
  527. # tcl_library to the first token of the virtual path.
  528. #
  529. proc ::safe::SyncAccessPath {slave} {
  530. namespace upvar ::safe S$slave state
  531. set slave_access_path $state(access_path,slave)
  532. ::interp eval $slave [list set auto_path $slave_access_path]
  533. Log $slave "auto_path in $slave has been set to $slave_access_path"\
  534. NOTICE
  535. # This code assumes that info library is the first element in the
  536. # list of auto_path's. See -> InterpSetConfig for the code which
  537. # ensures this condition.
  538. ::interp eval $slave [list \
  539. set tcl_library [lindex $slave_access_path 0]]
  540. }
  541. # Returns the virtual token for directory number N.
  542. proc ::safe::PathToken {n} {
  543. # We need to have a ":" in the token string so [file join] on the
  544. # mac won't turn it into a relative path.
  545. return "\$p(:$n:)" ;# Form tested by case 7.2
  546. }
  547. #
  548. # translate virtual path into real path
  549. #
  550. proc ::safe::TranslatePath {slave path} {
  551. namespace upvar ::safe S$slave state
  552. # somehow strip the namespaces 'functionality' out (the danger is that
  553. # we would strip valid macintosh "../" queries... :
  554. if {[string match "*::*" $path] || [string match "*..*" $path]} {
  555. return -code error "invalid characters in path $path"
  556. }
  557. # Use a cached map instead of computed local vars and subst.
  558. return [string map $state(access_path,map) $path]
  559. }
  560. # file name control (limit access to files/resources that should be a
  561. # valid tcl source file)
  562. proc ::safe::CheckFileName {slave file} {
  563. # This used to limit what can be sourced to ".tcl" and forbid files
  564. # with more than 1 dot and longer than 14 chars, but I changed that
  565. # for 8.4 as a safe interp has enough internal protection already to
  566. # allow sourcing anything. - hobbs
  567. if {![file exists $file]} {
  568. # don't tell the file path
  569. return -code error "no such file or directory"
  570. }
  571. if {![file readable $file]} {
  572. # don't tell the file path
  573. return -code error "not readable"
  574. }
  575. }
  576. # AliasGlob is the target of the "glob" alias in safe interpreters.
  577. proc ::safe::AliasGlob {slave args} {
  578. Log $slave "GLOB ! $args" NOTICE
  579. set cmd {}
  580. set at 0
  581. array set got {
  582. -directory 0
  583. -nocomplain 0
  584. -join 0
  585. -tails 0
  586. -- 0
  587. }
  588. if {$::tcl_platform(platform) eq "windows"} {
  589. set dirPartRE {^(.*)[\\/]([^\\/]*)$}
  590. } else {
  591. set dirPartRE {^(.*)/([^/]*)$}
  592. }
  593. set dir {}
  594. set virtualdir {}
  595. while {$at < [llength $args]} {
  596. switch -glob -- [set opt [lindex $args $at]] {
  597. -nocomplain - -- - -join - -tails {
  598. lappend cmd $opt
  599. set got($opt) 1
  600. incr at
  601. }
  602. -types - -type {
  603. lappend cmd -types [lindex $args [incr at]]
  604. incr at
  605. }
  606. -directory {
  607. if {$got($opt)} {
  608. return -code error \
  609. {"-directory" cannot be used with "-path"}
  610. }
  611. set got($opt) 1
  612. set virtualdir [lindex $args [incr at]]
  613. incr at
  614. }
  615. pkgIndex.tcl {
  616. # Oops, this is globbing a subdirectory in regular package
  617. # search. That is not wanted. Abort, handler does catch
  618. # already (because glob was not defined before). See
  619. # package.tcl, lines 484ff in tclPkgUnknown.
  620. return -code error "unknown command glob"
  621. }
  622. -* {
  623. Log $slave "Safe base rejecting glob option '$opt'"
  624. return -code error "Safe base rejecting glob option '$opt'"
  625. }
  626. default {
  627. break
  628. }
  629. }
  630. if {$got(--)} break
  631. }
  632. # Get the real path from the virtual one and check that the path is in the
  633. # access path of that slave. Done after basic argument processing so that
  634. # we know if -nocomplain is set.
  635. if {$got(-directory)} {
  636. if {[catch {
  637. set dir [TranslatePath $slave $virtualdir]
  638. DirInAccessPath $slave $dir
  639. } msg]} {
  640. Log $slave $msg
  641. if {$got(-nocomplain)} {
  642. return
  643. }
  644. return -code error "permission denied"
  645. }
  646. lappend cmd -directory $dir
  647. }
  648. # Apply the -join semantics ourselves
  649. if {$got(-join)} {
  650. set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
  651. }
  652. # Process remaining pattern arguments
  653. set firstPattern [llength $cmd]
  654. foreach opt [lrange $args $at end] {
  655. if {![regexp $dirPartRE $opt -> thedir thefile]} {
  656. set thedir .
  657. }
  658. if {$thedir eq "*"} {
  659. set mapped 0
  660. foreach d [glob -directory [TranslatePath $slave $virtualdir] \
  661. -types d -tails *] {
  662. catch {
  663. DirInAccessPath $slave \
  664. [TranslatePath $slave [file join $virtualdir $d]]
  665. if {$thefile eq "pkgIndex.tcl" || $thefile eq "*.tm"} {
  666. lappend cmd [file join $d $thefile]
  667. set mapped 1
  668. }
  669. }
  670. }
  671. if {$mapped} continue
  672. }
  673. if {[catch {
  674. set thedir [file join $virtualdir $thedir]
  675. DirInAccessPath $slave [TranslatePath $slave $thedir]
  676. } msg]} {
  677. Log $slave $msg
  678. if {$got(-nocomplain)} continue
  679. return -code error "permission denied"
  680. }
  681. lappend cmd $opt
  682. }
  683. Log $slave "GLOB = $cmd" NOTICE
  684. if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {
  685. return
  686. }
  687. if {[catch {
  688. ::interp invokehidden $slave glob {*}$cmd
  689. } msg]} {
  690. Log $slave $msg
  691. return -code error "script error"
  692. }
  693. Log $slave "GLOB < $msg" NOTICE
  694. # Translate path back to what the slave should see.
  695. set res {}
  696. set l [string length $dir]
  697. foreach p $msg {
  698. if {[string equal -length $l $dir $p]} {
  699. set p [string replace $p 0 [expr {$l-1}] $virtualdir]
  700. }
  701. lappend res $p
  702. }
  703. Log $slave "GLOB > $res" NOTICE
  704. return $res
  705. }
  706. # AliasSource is the target of the "source" alias in safe interpreters.
  707. proc ::safe::AliasSource {slave args} {
  708. set argc [llength $args]
  709. # Extended for handling of Tcl Modules to allow not only "source
  710. # filename", but "source -encoding E filename" as well.
  711. if {[lindex $args 0] eq "-encoding"} {
  712. incr argc -2
  713. set encoding [lindex $args 1]
  714. set at 2
  715. if {$encoding eq "identity"} {
  716. Log $slave "attempt to use the identity encoding"
  717. return -code error "permission denied"
  718. }
  719. } else {
  720. set at 0
  721. set encoding {}
  722. }
  723. if {$argc != 1} {
  724. set msg "wrong # args: should be \"source ?-encoding E? fileName\""
  725. Log $slave "$msg ($args)"
  726. return -code error $msg
  727. }
  728. set file [lindex $args $at]
  729. # get the real path from the virtual one.
  730. if {[catch {
  731. set realfile [TranslatePath $slave $file]
  732. } msg]} {
  733. Log $slave $msg
  734. return -code error "permission denied"
  735. }
  736. # check that the path is in the access path of that slave
  737. if {[catch {
  738. FileInAccessPath $slave $realfile
  739. } msg]} {
  740. Log $slave $msg
  741. return -code error "permission denied"
  742. }
  743. # do the checks on the filename :
  744. if {[catch {
  745. CheckFileName $slave $realfile
  746. } msg]} {
  747. Log $slave "$realfile:$msg"
  748. return -code error $msg
  749. }
  750. # Passed all the tests, lets source it. Note that we do this all manually
  751. # because we want to control [info script] in the slave so information
  752. # doesn't leak so much. [Bug 2913625]
  753. set old [::interp eval $slave {info script}]
  754. set code [catch {
  755. set f [open $realfile]
  756. fconfigure $f -eofchar \032
  757. if {$encoding ne ""} {
  758. fconfigure $f -encoding $encoding
  759. }
  760. set contents [read $f]
  761. close $f
  762. ::interp eval $slave [list info script $file]
  763. ::interp eval $slave $contents
  764. } msg opt]
  765. catch {interp eval $slave [list info script $old]}
  766. # Note that all non-errors are fine result codes from [source], so we must
  767. # take a little care to do it properly. [Bug 2923613]
  768. if {$code == 1} {
  769. Log $slave $msg
  770. return -code error "script error"
  771. }
  772. return -code $code -options $opt $msg
  773. }
  774. # AliasLoad is the target of the "load" alias in safe interpreters.
  775. proc ::safe::AliasLoad {slave file args} {
  776. set argc [llength $args]
  777. if {$argc > 2} {
  778. set msg "load error: too many arguments"
  779. Log $slave "$msg ($argc) {$file $args}"
  780. return -code error $msg
  781. }
  782. # package name (can be empty if file is not).
  783. set package [lindex $args 0]
  784. namespace upvar ::safe S$slave state
  785. # Determine where to load. load use a relative interp path and {}
  786. # means self, so we can directly and safely use passed arg.
  787. set target [lindex $args 1]
  788. if {$target ne ""} {
  789. # we will try to load into a sub sub interp; check that we want to
  790. # authorize that.
  791. if {!$state(nestedok)} {
  792. Log $slave "loading to a sub interp (nestedok)\
  793. disabled (trying to load $package to $target)"
  794. return -code error "permission denied (nested load)"
  795. }
  796. }
  797. # Determine what kind of load is requested
  798. if {$file eq ""} {
  799. # static package loading
  800. if {$package eq ""} {
  801. set msg "load error: empty filename and no package name"
  802. Log $slave $msg
  803. return -code error $msg
  804. }
  805. if {!$state(staticsok)} {
  806. Log $slave "static packages loading disabled\
  807. (trying to load $package to $target)"
  808. return -code error "permission denied (static package)"
  809. }
  810. } else {
  811. # file loading
  812. # get the real path from the virtual one.
  813. if {[catch {
  814. set file [TranslatePath $slave $file]
  815. } msg]} {
  816. Log $slave $msg
  817. return -code error "permission denied"
  818. }
  819. # check the translated path
  820. if {[catch {
  821. FileInAccessPath $slave $file
  822. } msg]} {
  823. Log $slave $msg
  824. return -code error "permission denied (path)"
  825. }
  826. }
  827. if {[catch {
  828. ::interp invokehidden $slave load $file $package $target
  829. } msg]} {
  830. Log $slave $msg
  831. return -code error $msg
  832. }
  833. return $msg
  834. }
  835. # FileInAccessPath raises an error if the file is not found in the list of
  836. # directories contained in the (master side recorded) slave's access path.
  837. # the security here relies on "file dirname" answering the proper
  838. # result... needs checking ?
  839. proc ::safe::FileInAccessPath {slave file} {
  840. namespace upvar ::safe S$slave state
  841. set access_path $state(access_path)
  842. if {[file isdirectory $file]} {
  843. return -code error "\"$file\": is a directory"
  844. }
  845. set parent [file dirname $file]
  846. # Normalize paths for comparison since lsearch knows nothing of
  847. # potential pathname anomalies.
  848. set norm_parent [file normalize $parent]
  849. namespace upvar ::safe S$slave state
  850. if {$norm_parent ni $state(access_path,norm)} {
  851. return -code error "\"$file\": not in access_path"
  852. }
  853. }
  854. proc ::safe::DirInAccessPath {slave dir} {
  855. namespace upvar ::safe S$slave state
  856. set access_path $state(access_path)
  857. if {[file isfile $dir]} {
  858. return -code error "\"$dir\": is a file"
  859. }
  860. # Normalize paths for comparison since lsearch knows nothing of
  861. # potential pathname anomalies.
  862. set norm_dir [file normalize $dir]
  863. namespace upvar ::safe S$slave state
  864. if {$norm_dir ni $state(access_path,norm)} {
  865. return -code error "\"$dir\": not in access_path"
  866. }
  867. }
  868. # This procedure enables access from a safe interpreter to only a subset
  869. # of the subcommands of a command:
  870. proc ::safe::Subset {slave command okpat args} {
  871. set subcommand [lindex $args 0]
  872. if {[regexp $okpat $subcommand]} {
  873. return [$command {*}$args]
  874. }
  875. set msg "not allowed to invoke subcommand $subcommand of $command"
  876. Log $slave $msg
  877. return -code error $msg
  878. }
  879. # This procedure installs an alias in a slave that invokes "safesubset" in
  880. # the master to execute allowed subcommands. It precomputes the pattern of
  881. # allowed subcommands; you can use wildcards in the pattern if you wish to
  882. # allow subcommand abbreviation.
  883. #
  884. # Syntax is: AliasSubset slave alias target subcommand1 subcommand2...
  885. proc ::safe::AliasSubset {slave alias target args} {
  886. set pat "^([join $args |])\$"
  887. ::interp alias $slave $alias {}\
  888. [namespace current]::Subset $slave $target $pat
  889. }
  890. # AliasEncoding is the target of the "encoding" alias in safe interpreters.
  891. proc ::safe::AliasEncoding {slave option args} {
  892. # Careful; do not want empty option to get through to the [string equal]
  893. if {[regexp {^(name.*|convert.*|)$} $option]} {
  894. return [::interp invokehidden $slave encoding $option {*}$args]
  895. }
  896. if {[string equal -length [string length $option] $option "system"]} {
  897. if {[llength $args] == 0} {
  898. # passed all the tests , lets source it:
  899. if {[catch {
  900. set sysenc [::interp invokehidden $slave encoding system]
  901. } msg]} {
  902. Log $slave $msg
  903. return -code error "script error"
  904. }
  905. return $sysenc
  906. }
  907. set msg "wrong # args: should be \"encoding system\""
  908. set code {TCL WRONGARGS}
  909. } else {
  910. set msg "bad option \"$option\": must be convertfrom, convertto, names, or system"
  911. set code [list TCL LOOKUP INDEX option $option]
  912. }
  913. Log $slave $msg
  914. return -code error -errorcode $code $msg
  915. }
  916. # Various minor hiding of platform features. [Bug 2913625]
  917. proc ::safe::AliasExeName {slave} {
  918. return ""
  919. }
  920. proc ::safe::Setup {} {
  921. ####
  922. #
  923. # Setup the arguments parsing
  924. #
  925. ####
  926. # Share the descriptions
  927. set temp [::tcl::OptKeyRegister {
  928. {-accessPath -list {} "access path for the slave"}
  929. {-noStatics "prevent loading of statically linked pkgs"}
  930. {-statics true "loading of statically linked pkgs"}
  931. {-nestedLoadOk "allow nested loading"}
  932. {-nested false "nested loading"}
  933. {-deleteHook -script {} "delete hook"}
  934. }]
  935. # create case (slave is optional)
  936. ::tcl::OptKeyRegister {
  937. {?slave? -name {} "name of the slave (optional)"}
  938. } ::safe::interpCreate
  939. # adding the flags sub programs to the command program (relying on Opt's
  940. # internal implementation details)
  941. lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)
  942. # init and configure (slave is needed)
  943. ::tcl::OptKeyRegister {
  944. {slave -name {} "name of the slave"}
  945. } ::safe::interpIC
  946. # adding the flags sub programs to the command program (relying on Opt's
  947. # internal implementation details)
  948. lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)
  949. # temp not needed anymore
  950. ::tcl::OptKeyDelete $temp
  951. ####
  952. #
  953. # Default: No logging.
  954. #
  955. ####
  956. setLogCmd {}
  957. # Log eventually.
  958. # To enable error logging, set Log to {puts stderr} for instance,
  959. # via setLogCmd.
  960. return
  961. }
  962. namespace eval ::safe {
  963. # internal variables
  964. # Log command, set via 'setLogCmd'. Logging is disabled when empty.
  965. variable Log {}
  966. # The package maintains a state array per slave interp under its
  967. # control. The name of this array is S<interp-name>. This array is
  968. # brought into scope where needed, using 'namespace upvar'. The S
  969. # prefix is used to avoid that a slave interp called "Log" smashes
  970. # the "Log" variable.
  971. #
  972. # The array's elements are:
  973. #
  974. # access_path : List of paths accessible to the slave.
  975. # access_path,norm : Ditto, in normalized form.
  976. # access_path,slave : Ditto, as the path tokens as seen by the slave.
  977. # access_path,map : dict ( token -> path )
  978. # access_path,remap : dict ( path -> token )
  979. # tm_path_slave : List of TM root directories, as tokens seen by the slave.
  980. # staticsok : Value of option -statics
  981. # nestedok : Value of option -nested
  982. # cleanupHook : Value of option -deleteHook
  983. }
  984. ::safe::Setup