- Got rid of newmodule.c
[python/dscho.git] / Lib / rexec.py
blob674e1932fa5cee06715631916e8013620219e945
1 """Restricted execution facilities.
3 The class RExec exports methods r_exec(), r_eval(), r_execfile(), and
4 r_import(), which correspond roughly to the built-in operations
5 exec, eval(), execfile() and import, but executing the code in an
6 environment that only exposes those built-in operations that are
7 deemed safe. To this end, a modest collection of 'fake' modules is
8 created which mimics the standard modules by the same names. It is a
9 policy decision which built-in modules and operations are made
10 available; this module provides a reasonable default, but derived
11 classes can change the policies e.g. by overriding or extending class
12 variables like ok_builtin_modules or methods like make_sys().
14 XXX To do:
15 - r_open should allow writing tmp dir
16 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
18 """
21 import sys
22 import __builtin__
23 import os
24 import ihooks
25 import imp
27 __all__ = ["RExec"]
29 class FileBase:
31 ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
32 'readlines', 'seek', 'tell', 'write', 'writelines')
35 class FileWrapper(FileBase):
37 # XXX This is just like a Bastion -- should use that!
39 def __init__(self, f):
40 self.f = f
41 for m in self.ok_file_methods:
42 if not hasattr(self, m) and hasattr(f, m):
43 setattr(self, m, getattr(f, m))
45 def close(self):
46 self.flush()
49 TEMPLATE = """
50 def %s(self, *args):
51 return apply(getattr(self.mod, self.name).%s, args)
52 """
54 class FileDelegate(FileBase):
56 def __init__(self, mod, name):
57 self.mod = mod
58 self.name = name
60 for m in FileBase.ok_file_methods + ('close',):
61 exec TEMPLATE % (m, m)
64 class RHooks(ihooks.Hooks):
66 def __init__(self, *args):
67 # Hacks to support both old and new interfaces:
68 # old interface was RHooks(rexec[, verbose])
69 # new interface is RHooks([verbose])
70 verbose = 0
71 rexec = None
72 if args and type(args[-1]) == type(0):
73 verbose = args[-1]
74 args = args[:-1]
75 if args and hasattr(args[0], '__class__'):
76 rexec = args[0]
77 args = args[1:]
78 if args:
79 raise TypeError, "too many arguments"
80 ihooks.Hooks.__init__(self, verbose)
81 self.rexec = rexec
83 def set_rexec(self, rexec):
84 # Called by RExec instance to complete initialization
85 self.rexec = rexec
87 def get_suffixes(self):
88 return self.rexec.get_suffixes()
90 def is_builtin(self, name):
91 return self.rexec.is_builtin(name)
93 def init_builtin(self, name):
94 m = __import__(name)
95 return self.rexec.copy_except(m, ())
97 def init_frozen(self, name): raise SystemError, "don't use this"
98 def load_source(self, *args): raise SystemError, "don't use this"
99 def load_compiled(self, *args): raise SystemError, "don't use this"
100 def load_package(self, *args): raise SystemError, "don't use this"
102 def load_dynamic(self, name, filename, file):
103 return self.rexec.load_dynamic(name, filename, file)
105 def add_module(self, name):
106 return self.rexec.add_module(name)
108 def modules_dict(self):
109 return self.rexec.modules
111 def default_path(self):
112 return self.rexec.modules['sys'].path
115 # XXX Backwards compatibility
116 RModuleLoader = ihooks.FancyModuleLoader
117 RModuleImporter = ihooks.ModuleImporter
120 class RExec(ihooks._Verbose):
121 """Basic restricted execution framework.
123 Code executed in this restricted environment will only have access to
124 modules and functions that are deemed safe; you can subclass RExec to
125 add or remove capabilities as desired.
127 The RExec class can prevent code from performing unsafe operations like
128 reading or writing disk files, or using TCP/IP sockets. However, it does
129 not protect against code using extremely large amounts of memory or
130 processor time.
134 ok_path = tuple(sys.path) # That's a policy decision
136 ok_builtin_modules = ('audioop', 'array', 'binascii',
137 'cmath', 'errno', 'imageop',
138 'marshal', 'math', 'md5', 'operator',
139 'parser', 'regex', 'pcre', 'rotor', 'select',
140 'sha', '_sre', 'strop', 'struct', 'time')
142 ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink',
143 'stat', 'times', 'uname', 'getpid', 'getppid',
144 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
146 ok_sys_names = ('ps1', 'ps2', 'copyright', 'version',
147 'platform', 'exit', 'maxint')
149 nok_builtin_names = ('open', 'file', 'reload', '__import__')
151 ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE)
153 def __init__(self, hooks = None, verbose = 0):
154 """Returns an instance of the RExec class.
156 The hooks parameter is an instance of the RHooks class or a subclass
157 of it. If it is omitted or None, the default RHooks class is
158 instantiated.
160 Whenever the RExec module searches for a module (even a built-in one)
161 or reads a module's code, it doesn't actually go out to the file
162 system itself. Rather, it calls methods of an RHooks instance that
163 was passed to or created by its constructor. (Actually, the RExec
164 object doesn't make these calls --- they are made by a module loader
165 object that's part of the RExec object. This allows another level of
166 flexibility, which can be useful when changing the mechanics of
167 import within the restricted environment.)
169 By providing an alternate RHooks object, we can control the file
170 system accesses made to import a module, without changing the
171 actual algorithm that controls the order in which those accesses are
172 made. For instance, we could substitute an RHooks object that
173 passes all filesystem requests to a file server elsewhere, via some
174 RPC mechanism such as ILU. Grail's applet loader uses this to support
175 importing applets from a URL for a directory.
177 If the verbose parameter is true, additional debugging output may be
178 sent to standard output.
181 ihooks._Verbose.__init__(self, verbose)
182 # XXX There's a circular reference here:
183 self.hooks = hooks or RHooks(verbose)
184 self.hooks.set_rexec(self)
185 self.modules = {}
186 self.ok_dynamic_modules = self.ok_builtin_modules
187 list = []
188 for mname in self.ok_builtin_modules:
189 if mname in sys.builtin_module_names:
190 list.append(mname)
191 self.ok_builtin_modules = tuple(list)
192 self.set_trusted_path()
193 self.make_builtin()
194 self.make_initial_modules()
195 # make_sys must be last because it adds the already created
196 # modules to its builtin_module_names
197 self.make_sys()
198 self.loader = RModuleLoader(self.hooks, verbose)
199 self.importer = RModuleImporter(self.loader, verbose)
201 def set_trusted_path(self):
202 # Set the path from which dynamic modules may be loaded.
203 # Those dynamic modules must also occur in ok_builtin_modules
204 self.trusted_path = filter(os.path.isabs, sys.path)
206 def load_dynamic(self, name, filename, file):
207 if name not in self.ok_dynamic_modules:
208 raise ImportError, "untrusted dynamic module: %s" % name
209 if name in sys.modules:
210 src = sys.modules[name]
211 else:
212 src = imp.load_dynamic(name, filename, file)
213 dst = self.copy_except(src, [])
214 return dst
216 def make_initial_modules(self):
217 self.make_main()
218 self.make_osname()
220 # Helpers for RHooks
222 def get_suffixes(self):
223 return [item # (suff, mode, type)
224 for item in imp.get_suffixes()
225 if item[2] in self.ok_file_types]
227 def is_builtin(self, mname):
228 return mname in self.ok_builtin_modules
230 # The make_* methods create specific built-in modules
232 def make_builtin(self):
233 m = self.copy_except(__builtin__, self.nok_builtin_names)
234 m.__import__ = self.r_import
235 m.reload = self.r_reload
236 m.open = m.file = self.r_open
238 def make_main(self):
239 m = self.add_module('__main__')
241 def make_osname(self):
242 osname = os.name
243 src = __import__(osname)
244 dst = self.copy_only(src, self.ok_posix_names)
245 dst.environ = e = {}
246 for key, value in os.environ.items():
247 e[key] = value
249 def make_sys(self):
250 m = self.copy_only(sys, self.ok_sys_names)
251 m.modules = self.modules
252 m.argv = ['RESTRICTED']
253 m.path = map(None, self.ok_path)
254 m.exc_info = self.r_exc_info
255 m = self.modules['sys']
256 l = self.modules.keys() + list(self.ok_builtin_modules)
257 l.sort()
258 m.builtin_module_names = tuple(l)
260 # The copy_* methods copy existing modules with some changes
262 def copy_except(self, src, exceptions):
263 dst = self.copy_none(src)
264 for name in dir(src):
265 setattr(dst, name, getattr(src, name))
266 for name in exceptions:
267 try:
268 delattr(dst, name)
269 except AttributeError:
270 pass
271 return dst
273 def copy_only(self, src, names):
274 dst = self.copy_none(src)
275 for name in names:
276 try:
277 value = getattr(src, name)
278 except AttributeError:
279 continue
280 setattr(dst, name, value)
281 return dst
283 def copy_none(self, src):
284 m = self.add_module(src.__name__)
285 m.__doc__ = src.__doc__
286 return m
288 # Add a module -- return an existing module or create one
290 def add_module(self, mname):
291 if mname in self.modules:
292 return self.modules[mname]
293 self.modules[mname] = m = self.hooks.new_module(mname)
294 m.__builtins__ = self.modules['__builtin__']
295 return m
297 # The r* methods are public interfaces
299 def r_exec(self, code):
300 """Execute code within a restricted environment.
302 The code parameter must either be a string containing one or more
303 lines of Python code, or a compiled code object, which will be
304 executed in the restricted environment's __main__ module.
307 m = self.add_module('__main__')
308 exec code in m.__dict__
310 def r_eval(self, code):
311 """Evaluate code within a restricted environment.
313 The code parameter must either be a string containing a Python
314 expression, or a compiled code object, which will be evaluated in
315 the restricted environment's __main__ module. The value of the
316 expression or code object will be returned.
319 m = self.add_module('__main__')
320 return eval(code, m.__dict__)
322 def r_execfile(self, file):
323 """Execute the Python code in the file in the restricted
324 environment's __main__ module.
327 m = self.add_module('__main__')
328 execfile(file, m.__dict__)
330 def r_import(self, mname, globals={}, locals={}, fromlist=[]):
331 """Import a module, raising an ImportError exception if the module
332 is considered unsafe.
334 This method is implicitly called by code executing in the
335 restricted environment. Overriding this method in a subclass is
336 used to change the policies enforced by a restricted environment.
339 return self.importer.import_module(mname, globals, locals, fromlist)
341 def r_reload(self, m):
342 """Reload the module object, re-parsing and re-initializing it.
344 This method is implicitly called by code executing in the
345 restricted environment. Overriding this method in a subclass is
346 used to change the policies enforced by a restricted environment.
349 return self.importer.reload(m)
351 def r_unload(self, m):
352 """Unload the module.
354 Removes it from the restricted environment's sys.modules dictionary.
356 This method is implicitly called by code executing in the
357 restricted environment. Overriding this method in a subclass is
358 used to change the policies enforced by a restricted environment.
361 return self.importer.unload(m)
363 # The s_* methods are similar but also swap std{in,out,err}
365 def make_delegate_files(self):
366 s = self.modules['sys']
367 self.delegate_stdin = FileDelegate(s, 'stdin')
368 self.delegate_stdout = FileDelegate(s, 'stdout')
369 self.delegate_stderr = FileDelegate(s, 'stderr')
370 self.restricted_stdin = FileWrapper(sys.stdin)
371 self.restricted_stdout = FileWrapper(sys.stdout)
372 self.restricted_stderr = FileWrapper(sys.stderr)
374 def set_files(self):
375 if not hasattr(self, 'save_stdin'):
376 self.save_files()
377 if not hasattr(self, 'delegate_stdin'):
378 self.make_delegate_files()
379 s = self.modules['sys']
380 s.stdin = self.restricted_stdin
381 s.stdout = self.restricted_stdout
382 s.stderr = self.restricted_stderr
383 sys.stdin = self.delegate_stdin
384 sys.stdout = self.delegate_stdout
385 sys.stderr = self.delegate_stderr
387 def reset_files(self):
388 self.restore_files()
389 s = self.modules['sys']
390 self.restricted_stdin = s.stdin
391 self.restricted_stdout = s.stdout
392 self.restricted_stderr = s.stderr
395 def save_files(self):
396 self.save_stdin = sys.stdin
397 self.save_stdout = sys.stdout
398 self.save_stderr = sys.stderr
400 def restore_files(self):
401 sys.stdin = self.save_stdin
402 sys.stdout = self.save_stdout
403 sys.stderr = self.save_stderr
405 def s_apply(self, func, args=(), kw=None):
406 self.save_files()
407 try:
408 self.set_files()
409 if kw:
410 r = apply(func, args, kw)
411 else:
412 r = apply(func, args)
413 finally:
414 self.restore_files()
415 return r
417 def s_exec(self, *args):
418 """Execute code within a restricted environment.
420 Similar to the r_exec() method, but the code will be granted access
421 to restricted versions of the standard I/O streams sys.stdin,
422 sys.stderr, and sys.stdout.
424 The code parameter must either be a string containing one or more
425 lines of Python code, or a compiled code object, which will be
426 executed in the restricted environment's __main__ module.
429 return self.s_apply(self.r_exec, args)
431 def s_eval(self, *args):
432 """Evaluate code within a restricted environment.
434 Similar to the r_eval() method, but the code will be granted access
435 to restricted versions of the standard I/O streams sys.stdin,
436 sys.stderr, and sys.stdout.
438 The code parameter must either be a string containing a Python
439 expression, or a compiled code object, which will be evaluated in
440 the restricted environment's __main__ module. The value of the
441 expression or code object will be returned.
444 return self.s_apply(self.r_eval, args)
446 def s_execfile(self, *args):
447 """Execute the Python code in the file in the restricted
448 environment's __main__ module.
450 Similar to the r_execfile() method, but the code will be granted
451 access to restricted versions of the standard I/O streams sys.stdin,
452 sys.stderr, and sys.stdout.
455 return self.s_apply(self.r_execfile, args)
457 def s_import(self, *args):
458 """Import a module, raising an ImportError exception if the module
459 is considered unsafe.
461 This method is implicitly called by code executing in the
462 restricted environment. Overriding this method in a subclass is
463 used to change the policies enforced by a restricted environment.
465 Similar to the r_import() method, but has access to restricted
466 versions of the standard I/O streams sys.stdin, sys.stderr, and
467 sys.stdout.
470 return self.s_apply(self.r_import, args)
472 def s_reload(self, *args):
473 """Reload the module object, re-parsing and re-initializing it.
475 This method is implicitly called by code executing in the
476 restricted environment. Overriding this method in a subclass is
477 used to change the policies enforced by a restricted environment.
479 Similar to the r_reload() method, but has access to restricted
480 versions of the standard I/O streams sys.stdin, sys.stderr, and
481 sys.stdout.
484 return self.s_apply(self.r_reload, args)
486 def s_unload(self, *args):
487 """Unload the module.
489 Removes it from the restricted environment's sys.modules dictionary.
491 This method is implicitly called by code executing in the
492 restricted environment. Overriding this method in a subclass is
493 used to change the policies enforced by a restricted environment.
495 Similar to the r_unload() method, but has access to restricted
496 versions of the standard I/O streams sys.stdin, sys.stderr, and
497 sys.stdout.
500 return self.s_apply(self.r_unload, args)
502 # Restricted open(...)
504 def r_open(self, file, mode='r', buf=-1):
505 """Method called when open() is called in the restricted environment.
507 The arguments are identical to those of the open() function, and a
508 file object (or a class instance compatible with file objects)
509 should be returned. RExec's default behaviour is allow opening
510 any file for reading, but forbidding any attempt to write a file.
512 This method is implicitly called by code executing in the
513 restricted environment. Overriding this method in a subclass is
514 used to change the policies enforced by a restricted environment.
517 if mode not in ('r', 'rb'):
518 raise IOError, "can't open files for writing in restricted mode"
519 return open(file, mode, buf)
521 # Restricted version of sys.exc_info()
523 def r_exc_info(self):
524 ty, va, tr = sys.exc_info()
525 tr = None
526 return ty, va, tr
529 def test():
530 import getopt, traceback
531 opts, args = getopt.getopt(sys.argv[1:], 'vt:')
532 verbose = 0
533 trusted = []
534 for o, a in opts:
535 if o == '-v':
536 verbose = verbose+1
537 if o == '-t':
538 trusted.append(a)
539 r = RExec(verbose=verbose)
540 if trusted:
541 r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted)
542 if args:
543 r.modules['sys'].argv = args
544 r.modules['sys'].path.insert(0, os.path.dirname(args[0]))
545 else:
546 r.modules['sys'].path.insert(0, "")
547 fp = sys.stdin
548 if args and args[0] != '-':
549 try:
550 fp = open(args[0])
551 except IOError, msg:
552 print "%s: can't open file %s" % (sys.argv[0], `args[0]`)
553 return 1
554 if fp.isatty():
555 import code
556 try:
557 code.interact(
558 "*** RESTRICTED *** Python %s on %s\n"
559 'Type "help", "copyright", "credits" or "license" '
560 "for more information." % (sys.version, sys.platform),
561 local=r.modules['__main__'].__dict__)
562 except SystemExit, n:
563 return n
564 else:
565 text = fp.read()
566 fp.close()
567 c = compile(text, fp.name, 'exec')
568 try:
569 r.s_exec(c)
570 except SystemExit, n:
571 return n
572 except:
573 traceback.print_exc()
574 return 1
577 if __name__ == '__main__':
578 sys.exit(test())