Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: data/Media/tcl8.5/safe.tcl @ 5230

Last change on this file since 5230 was 5167, checked in by rgrieder, 16 years ago

added svn property svn:eol-style native to all tcl files

  • Property svn:eol-style set to native
File size: 26.3 KB
Line 
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
25package require opt 0.4.1;
26
27# Create the safe namespace
28namespace 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 "$")
388proc ::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 "$").
401proc ::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
506proc ::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
546proc ::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}
Note: See TracBrowser for help on using the repository browser.