safe.tcl 33 KB

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