Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: data/trunk/Media/tcl8.4/safe.tcl @ 5180

Last change on this file since 5180 was 5180, checked in by dafrick, 16 years ago
File size: 27.0 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.9.2.3 2005/07/22 21:59:41 dgp 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        # We don't try to use the -rsrc on the mac because it would get
470        # confusing if you would want to customize init.tcl
471        # for a given set of safe slaves, on all the platforms
472        # you just need to give a specific access_path and
473        # the mac should be no exception. As there is no
474        # obvious full "safe ressources" design nor implementation
475        # for the mac, safe interps there will just don't
476        # have that ability. (A specific app can still reenable
477        # that using custom aliases if they want to).
478        # It would also make the security analysis and the Safe Tcl security
479        # model platform dependant and thus more error prone.
480
481        if {[catch {::interp eval $slave\
482                {source [file join $tcl_library init.tcl]}} msg]} {
483            Log $slave "can't source init.tcl ($msg)"
484            error "can't source init.tcl into slave $slave ($msg)"
485        }
486
487        return $slave
488    }
489
490
491    # Add (only if needed, avoid duplicates) 1 level of
492    # sub directories to an existing path list.
493    # Also removes non directories from the returned list.
494    proc AddSubDirs {pathList} {
495        set res {}
496        foreach dir $pathList {
497            if {[file isdirectory $dir]} {
498                # check that we don't have it yet as a children
499                # of a previous dir
500                if {[lsearch -exact $res $dir]<0} {
501                    lappend res $dir
502                }
503                foreach sub [glob -directory $dir -nocomplain *] {
504                    if {([file isdirectory $sub]) \
505                            && ([lsearch -exact $res $sub]<0) } {
506                        # new sub dir, add it !
507                        lappend res $sub
508                    }
509                }
510            }
511        }
512        return $res
513    }
514
515    # This procedure deletes a safe slave managed by Safe Tcl and
516    # cleans up associated state:
517
518proc ::safe::interpDelete {slave} {
519
520        Log $slave "About to delete" NOTICE
521
522        # If the slave has a cleanup hook registered, call it.
523        # check the existance because we might be called to delete an interp
524        # which has not been registered with us at all
525        set hookname [DeleteHookName $slave]
526        if {[Exists $hookname]} {
527            set hook [Set $hookname]
528            if {![::tcl::Lempty $hook]} {
529                # remove the hook now, otherwise if the hook
530                # calls us somehow, we'll loop
531                Unset $hookname
532                if {[catch {eval $hook [list $slave]} err]} {
533                    Log $slave "Delete hook error ($err)"
534                }
535            }
536        }
537
538        # Discard the global array of state associated with the slave, and
539        # delete the interpreter.
540
541        set statename [InterpStateName $slave]
542        if {[Exists $statename]} {
543            Unset $statename
544        }
545
546        # if we have been called twice, the interp might have been deleted
547        # already
548        if {[::interp exists $slave]} {
549            ::interp delete $slave
550            Log $slave "Deleted" NOTICE
551        }
552
553        return
554    }
555
556    # Set (or get) the loging mecanism
557
558proc ::safe::setLogCmd {args} {
559    variable Log
560    if {[llength $args] == 0} {
561        return $Log
562    } else {
563        if {[llength $args] == 1} {
564            set Log [lindex $args 0]
565        } else {
566            set Log $args
567        }
568    }
569}
570
571    # internal variable
572    variable Log {}
573
574    # ------------------- END OF PUBLIC METHODS ------------
575
576
577    #
578    # sets the slave auto_path to the master recorded value.
579    # also sets tcl_library to the first token of the virtual path.
580    #
581    proc SyncAccessPath {slave} {
582        set slave_auto_path [Set [VirtualPathListName $slave]]
583        ::interp eval $slave [list set auto_path $slave_auto_path]
584        Log $slave "auto_path in $slave has been set to $slave_auto_path"\
585                NOTICE
586        ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]]
587    }
588
589    # base name for storing all the slave states
590    # the array variable name for slave foo is thus "Sfoo"
591    # and for sub slave {foo bar} "Sfoo bar" (spaces are handled
592    # ok everywhere (or should))
593    # We add the S prefix to avoid that a slave interp called "Log"
594    # would smash our "Log" variable.
595    proc InterpStateName {slave} {
596        return "S$slave"
597    }
598
599    # Check that the given slave is "one of us"
600    proc IsInterp {slave} {
601        expr {[Exists [InterpStateName $slave]] && [::interp exists $slave]}
602    }
603
604    # returns the virtual token for directory number N
605    # if the slave argument is given,
606    # it will return the corresponding master global variable name
607    proc PathToken {n {slave ""}} {
608        if {$slave ne ""} {
609            return "[InterpStateName $slave](access_path,$n)"
610        } else {
611            # We need to have a ":" in the token string so
612            # [file join] on the mac won't turn it into a relative
613            # path.
614            return "p(:$n:)"
615        }
616    }
617    # returns the variable name of the complete path list
618    proc PathListName {slave} {
619        return "[InterpStateName $slave](access_path)"
620    }
621    # returns the variable name of the complete path list
622    proc VirtualPathListName {slave} {
623        return "[InterpStateName $slave](access_path_slave)"
624    }
625    # returns the variable name of the number of items
626    proc PathNumberName {slave} {
627        return "[InterpStateName $slave](access_path,n)"
628    }
629    # returns the staticsok flag var name
630    proc StaticsOkName {slave} {
631        return "[InterpStateName $slave](staticsok)"
632    }
633    # returns the nestedok flag var name
634    proc NestedOkName {slave} {
635        return "[InterpStateName $slave](nestedok)"
636    }
637    # Run some code at the namespace toplevel
638    proc Toplevel {args} {
639        namespace eval [namespace current] $args
640    }
641    # set/get values
642    proc Set {args} {
643        eval [linsert $args 0 Toplevel set]
644    }
645    # lappend on toplevel vars
646    proc Lappend {args} {
647        eval [linsert $args 0 Toplevel lappend]
648    }
649    # unset a var/token (currently just an global level eval)
650    proc Unset {args} {
651        eval [linsert $args 0 Toplevel unset]
652    }
653    # test existance
654    proc Exists {varname} {
655        Toplevel info exists $varname
656    }
657    # short cut for access path getting
658    proc GetAccessPath {slave} {
659        Set [PathListName $slave]
660    }
661    # short cut for statics ok flag getting
662    proc StaticsOk {slave} {
663        Set [StaticsOkName $slave]
664    }
665    # short cut for getting the multiples interps sub loading ok flag
666    proc NestedOk {slave} {
667        Set [NestedOkName $slave]
668    }
669    # interp deletion storing hook name
670    proc DeleteHookName {slave} {
671        return [InterpStateName $slave](cleanupHook)
672    }
673
674    #
675    # translate virtual path into real path
676    #
677    proc TranslatePath {slave path} {
678        # somehow strip the namespaces 'functionality' out (the danger
679        # is that we would strip valid macintosh "../" queries... :
680        if {[regexp {(::)|(\.\.)} $path]} {
681            error "invalid characters in path $path"
682        }
683        set n [expr {[Set [PathNumberName $slave]]-1}]
684        for {} {$n>=0} {incr n -1} {
685            # fill the token virtual names with their real value
686            set [PathToken $n] [Set [PathToken $n $slave]]
687        }
688        # replaces the token by their value
689        subst -nobackslashes -nocommands $path
690    }
691
692
693    # Log eventually log an error
694    # to enable error logging, set Log to {puts stderr} for instance
695    proc Log {slave msg {type ERROR}} {
696        variable Log
697        if {[info exists Log] && [llength $Log]} {
698            eval $Log [list "$type for slave $slave : $msg"]
699        }
700    }
701
702
703    # file name control (limit access to files/ressources that should be
704    # a valid tcl source file)
705    proc CheckFileName {slave file} {
706        # This used to limit what can be sourced to ".tcl" and forbid files
707        # with more than 1 dot and longer than 14 chars, but I changed that
708        # for 8.4 as a safe interp has enough internal protection already
709        # to allow sourcing anything. - hobbs
710
711        if {![file exists $file]} {
712            # don't tell the file path
713            error "no such file or directory"
714        }
715
716        if {![file readable $file]} {
717            # don't tell the file path
718            error "not readable"
719        }
720    }
721
722
723    # AliasSource is the target of the "source" alias in safe interpreters.
724
725    proc AliasSource {slave args} {
726
727        set argc [llength $args]
728        # Allow only "source filename"
729        # (and not mac specific -rsrc for instance - see comment in ::init
730        # for current rationale)
731        if {$argc != 1} {
732            set msg "wrong # args: should be \"source fileName\""
733            Log $slave "$msg ($args)"
734            return -code error $msg
735        }
736        set file [lindex $args 0]
737       
738        # get the real path from the virtual one.
739        if {[catch {set file [TranslatePath $slave $file]} msg]} {
740            Log $slave $msg
741            return -code error "permission denied"
742        }
743       
744        # check that the path is in the access path of that slave
745        if {[catch {FileInAccessPath $slave $file} msg]} {
746            Log $slave $msg
747            return -code error "permission denied"
748        }
749
750        # do the checks on the filename :
751        if {[catch {CheckFileName $slave $file} msg]} {
752            Log $slave "$file:$msg"
753            return -code error $msg
754        }
755
756        # passed all the tests , lets source it:
757        if {[catch {::interp invokehidden $slave source $file} msg]} {
758            Log $slave $msg
759            return -code error "script error"
760        }
761        return $msg
762    }
763
764    # AliasLoad is the target of the "load" alias in safe interpreters.
765
766    proc AliasLoad {slave file args} {
767
768        set argc [llength $args]
769        if {$argc > 2} {
770            set msg "load error: too many arguments"
771            Log $slave "$msg ($argc) {$file $args}"
772            return -code error $msg
773        }
774
775        # package name (can be empty if file is not).
776        set package [lindex $args 0]
777
778        # Determine where to load. load use a relative interp path
779        # and {} means self, so we can directly and safely use passed arg.
780        set target [lindex $args 1]
781        if {$target ne ""} {
782            # we will try to load into a sub sub interp
783            # check that we want to authorize that.
784            if {![NestedOk $slave]} {
785                Log $slave "loading to a sub interp (nestedok)\
786                        disabled (trying to load $package to $target)"
787                return -code error "permission denied (nested load)"
788            }
789           
790        }
791
792        # Determine what kind of load is requested
793        if {$file eq ""} {
794            # static package loading
795            if {$package eq ""} {
796                set msg "load error: empty filename and no package name"
797                Log $slave $msg
798                return -code error $msg
799            }
800            if {![StaticsOk $slave]} {
801                Log $slave "static packages loading disabled\
802                        (trying to load $package to $target)"
803                return -code error "permission denied (static package)"
804            }
805        } else {
806            # file loading
807
808            # get the real path from the virtual one.
809            if {[catch {set file [TranslatePath $slave $file]} msg]} {
810                Log $slave $msg
811                return -code error "permission denied"
812            }
813
814            # check the translated path
815            if {[catch {FileInAccessPath $slave $file} msg]} {
816                Log $slave $msg
817                return -code error "permission denied (path)"
818            }
819        }
820
821        if {[catch {::interp invokehidden\
822                $slave load $file $package $target} msg]} {
823            Log $slave $msg
824            return -code error $msg
825        }
826
827        return $msg
828    }
829
830    # FileInAccessPath raises an error if the file is not found in
831    # the list of directories contained in the (master side recorded) slave's
832    # access path.
833
834    # the security here relies on "file dirname" answering the proper
835    # result.... needs checking ?
836    proc FileInAccessPath {slave file} {
837
838        set access_path [GetAccessPath $slave]
839
840        if {[file isdirectory $file]} {
841            error "\"$file\": is a directory"
842        }
843        set parent [file dirname $file]
844
845        # Normalize paths for comparison since lsearch knows nothing of
846        # potential pathname anomalies.
847        set norm_parent [file normalize $parent]
848        foreach path $access_path {
849            lappend norm_access_path [file normalize $path]
850        }
851
852        if {[lsearch -exact $norm_access_path $norm_parent] == -1} {
853            error "\"$file\": not in access_path"
854        }
855    }
856
857    # This procedure enables access from a safe interpreter to only a subset of
858    # the subcommands of a command:
859
860    proc Subset {slave command okpat args} {
861        set subcommand [lindex $args 0]
862        if {[regexp $okpat $subcommand]} {
863            return [eval [linsert $args 0 $command]]
864        }
865        set msg "not allowed to invoke subcommand $subcommand of $command"
866        Log $slave $msg
867        error $msg
868    }
869
870    # This procedure installs an alias in a slave that invokes "safesubset"
871    # in the master to execute allowed subcommands. It precomputes the pattern
872    # of allowed subcommands; you can use wildcards in the pattern if you wish
873    # to allow subcommand abbreviation.
874    #
875    # Syntax is: AliasSubset slave alias target subcommand1 subcommand2...
876
877    proc AliasSubset {slave alias target args} {
878        set pat ^(; set sep ""
879        foreach sub $args {
880            append pat $sep$sub
881            set sep |
882        }
883        append pat )\$
884        ::interp alias $slave $alias {}\
885                [namespace current]::Subset $slave $target $pat
886    }
887
888    # AliasEncoding is the target of the "encoding" alias in safe interpreters.
889
890    proc AliasEncoding {slave args} {
891
892        set argc [llength $args]
893
894        set okpat "^(name.*|convert.*)\$"
895        set subcommand [lindex $args 0]
896
897        if {[regexp $okpat $subcommand]} {
898            return [eval [linsert $args 0 \
899                    ::interp invokehidden $slave encoding]]
900        }
901
902        if {[string first $subcommand system] == 0} {
903            if {$argc == 1} {
904                # passed all the tests , lets source it:
905                if {[catch {::interp invokehidden \
906                        $slave encoding system} msg]} {
907                    Log $slave $msg
908                    return -code error "script error"
909                }
910            } else {
911                set msg "wrong # args: should be \"encoding system\""
912                Log $slave $msg
913                error $msg
914            }
915        } else {
916            set msg "wrong # args: should be \"encoding option ?arg ...?\""
917            Log $slave $msg
918            error $msg
919        }
920
921        return $msg
922    }
923
924}
Note: See TracBrowser for help on using the repository browser.