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