import os from zope.interface import implementer from twisted.internet import defer from twisted.python import log as twisted_log from allmydata.interfaces import IFileNode, IFilesystemNode from allmydata.util import base32 from allmydata.util.encodingutil import quote_output class FileProhibited(Exception): """This client has been configured to prohibit access to this object.""" def __init__(self, reason): Exception.__init__(self, "Access Prohibited: %s" % quote_output(reason, encoding='utf-8', quotemarks=False)) self.reason = reason class Blacklist(object): def __init__(self, blacklist_fn): self.blacklist_fn = blacklist_fn self.last_mtime = None self.entries = {} self.read_blacklist() # sets .last_mtime and .entries def read_blacklist(self): try: current_mtime = os.stat(self.blacklist_fn).st_mtime except EnvironmentError: # unreadable blacklist file means no blacklist self.entries.clear() return try: if self.last_mtime is None or current_mtime > self.last_mtime: self.entries.clear() with open(self.blacklist_fn, "r") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue si_s, reason = line.split(None, 1) si = base32.a2b(si_s) # must be valid base32 self.entries[si] = reason self.last_mtime = current_mtime except Exception as e: twisted_log.err(e, "unparseable blacklist file") raise def check_storageindex(self, si): self.read_blacklist() reason = self.entries.get(si, None) if reason is not None: # log this to logs/twistd.log, since web logs go there too twisted_log.msg("blacklist prohibited access to SI %s: %s" % (base32.b2a(si), reason)) return reason @implementer(IFileNode) class ProhibitedNode(object): def __init__(self, wrapped_node, reason): assert IFilesystemNode.providedBy(wrapped_node), wrapped_node self.wrapped_node = wrapped_node self.reason = reason def get_cap(self): return self.wrapped_node.get_cap() def get_readcap(self): return self.wrapped_node.get_readcap() def is_readonly(self): return self.wrapped_node.is_readonly() def is_mutable(self): return self.wrapped_node.is_mutable() def is_unknown(self): return self.wrapped_node.is_unknown() def is_allowed_in_immutable_directory(self): return self.wrapped_node.is_allowed_in_immutable_directory() def is_alleged_immutable(self): return self.wrapped_node.is_alleged_immutable() def raise_error(self): # We don't raise an exception here because that would prevent the node from being listed. pass def get_uri(self): return self.wrapped_node.get_uri() def get_write_uri(self): return self.wrapped_node.get_write_uri() def get_readonly_uri(self): return self.wrapped_node.get_readonly_uri() def get_storage_index(self): return self.wrapped_node.get_storage_index() def get_verify_cap(self): return self.wrapped_node.get_verify_cap() def get_repair_cap(self): return self.wrapped_node.get_repair_cap() def get_size(self): return None def get_current_size(self): return defer.succeed(None) def get_size_of_best_version(self): return defer.succeed(None) def check(self, monitor, verify, add_lease): return defer.succeed(None) def check_and_repair(self, monitor, verify, add_lease): return defer.succeed(None) def get_version(self): return None # Omitting any of these methods would fail safe; they are just to ensure correct error reporting. def get_best_readable_version(self): raise FileProhibited(self.reason) def download_best_version(self, progress=None): raise FileProhibited(self.reason) def get_best_mutable_version(self): raise FileProhibited(self.reason) def overwrite(self, new_contents): raise FileProhibited(self.reason) def modify(self, modifier_cb): raise FileProhibited(self.reason) def get_servermap(self, mode): raise FileProhibited(self.reason) def download_version(self, servermap, version): raise FileProhibited(self.reason) def upload(self, new_contents, servermap): raise FileProhibited(self.reason) def get_writekey(self): raise FileProhibited(self.reason) def read(self, consumer, offset=0, size=None): raise FileProhibited(self.reason)