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