From 05022dca36780b3bfd3faba1f05b557178535f78 Mon Sep 17 00:00:00 2001
From: david-sarah <david-sarah@jacaranda.org>
Date: Sun, 11 Jul 2010 14:37:21 -0700
Subject: [PATCH] Move EncryptedTemporaryFile from SFTP frontend to
 allmydata.util.fileutil, and make the FTP frontend also use it (fixing
 #1083).

---
 src/allmydata/frontends/ftpd.py  |  4 +--
 src/allmydata/frontends/sftpd.py | 55 ++------------------------------
 src/allmydata/util/fileutil.py   | 48 +++++++++++++++++++++++++++-
 3 files changed, 51 insertions(+), 56 deletions(-)

diff --git a/src/allmydata/frontends/ftpd.py b/src/allmydata/frontends/ftpd.py
index 8e66e437..1321863a 100644
--- a/src/allmydata/frontends/ftpd.py
+++ b/src/allmydata/frontends/ftpd.py
@@ -1,5 +1,4 @@
 
-import tempfile
 from zope.interface import implements
 from twisted.application import service, strports
 from twisted.internet import defer
@@ -10,6 +9,7 @@ from twisted.protocols import ftp
 from allmydata.interfaces import IDirectoryNode, ExistingChildError, \
      NoSuchChildError
 from allmydata.immutable.upload import FileHandle
+from allmydata.util.fileutil import EncryptedTemporaryFile
 
 class ReadFile:
     implements(ftp.IReadFile)
@@ -27,7 +27,7 @@ class FileWriter:
             raise NotImplementedError("Non-streaming producer not supported.")
         # we write the data to a temporary file, since Tahoe can't do
         # streaming upload yet.
-        self.f = tempfile.TemporaryFile()
+        self.f = EncryptedTemporaryFile()
         return None
 
     def unregisterProducer(self):
diff --git a/src/allmydata/frontends/sftpd.py b/src/allmydata/frontends/sftpd.py
index 5980effb..a38196fc 100644
--- a/src/allmydata/frontends/sftpd.py
+++ b/src/allmydata/frontends/sftpd.py
@@ -1,5 +1,5 @@
 
-import os, tempfile, heapq, binascii, traceback, array, stat, struct
+import heapq, traceback, array, stat, struct
 from types import NoneType
 from stat import S_IFREG, S_IFDIR
 from time import time, strftime, localtime
@@ -32,8 +32,7 @@ from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError,
 from allmydata.mutable.common import NotWriteableError
 from allmydata.immutable.upload import FileHandle
 from allmydata.dirnode import update_metadata
-
-from pycryptopp.cipher.aes import AES
+from allmydata.util.fileutil import EncryptedTemporaryFile
 
 noisy = True
 use_foolscap_logging = True
@@ -288,56 +287,6 @@ def _direntry_for(filenode_or_parent, childname, filenode=None):
     return None
 
 
-class EncryptedTemporaryFile(PrefixingLogMixin):
-    # not implemented: next, readline, readlines, xreadlines, writelines
-
-    def __init__(self):
-        PrefixingLogMixin.__init__(self, facility="tahoe.sftp")
-        self.file = tempfile.TemporaryFile()
-        self.key = os.urandom(16)  # AES-128
-
-    def _crypt(self, offset, data):
-        # TODO: use random-access AES (pycryptopp ticket #18)
-        offset_big = offset // 16
-        offset_small = offset % 16
-        iv = binascii.unhexlify("%032x" % offset_big)
-        cipher = AES(self.key, iv=iv)
-        cipher.process("\x00"*offset_small)
-        return cipher.process(data)
-
-    def close(self):
-        self.file.close()
-
-    def flush(self):
-        self.file.flush()
-
-    def seek(self, offset, whence=0):  # 0 = SEEK_SET
-        if noisy: self.log(".seek(%r, %r)" % (offset, whence), level=NOISY)
-        self.file.seek(offset, whence)
-
-    def tell(self):
-        offset = self.file.tell()
-        if noisy: self.log(".tell() = %r" % (offset,), level=NOISY)
-        return offset
-
-    def read(self, size=-1):
-        if noisy: self.log(".read(%r)" % (size,), level=NOISY)
-        index = self.file.tell()
-        ciphertext = self.file.read(size)
-        plaintext = self._crypt(index, ciphertext)
-        return plaintext
-
-    def write(self, plaintext):
-        if noisy: self.log(".write(<data of length %r>)" % (len(plaintext),), level=NOISY)
-        index = self.file.tell()
-        ciphertext = self._crypt(index, plaintext)
-        self.file.write(ciphertext)
-
-    def truncate(self, newsize):
-        if noisy: self.log(".truncate(%r)" % (newsize,), level=NOISY)
-        self.file.truncate(newsize)
-
-
 class OverwriteableFileConsumer(PrefixingLogMixin):
     implements(IFinishableConsumer)
     """I act both as a consumer for the download of the original file contents, and as a
diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py
index 46e1c9bf..f671f7ea 100644
--- a/src/allmydata/util/fileutil.py
+++ b/src/allmydata/util/fileutil.py
@@ -2,10 +2,13 @@
 Futz with files like a pro.
 """
 
-import sys, exceptions, os, stat, tempfile, time
+import sys, exceptions, os, stat, tempfile, time, binascii
 
 from twisted.python import log
 
+from pycryptopp.cipher.aes import AES
+
+
 def rename(src, dst, tries=4, basedelay=0.1):
     """ Here is a superkludge to workaround the fact that occasionally on
     Windows some other process (e.g. an anti-virus scanner, a local search
@@ -112,6 +115,49 @@ class NamedTemporaryDirectory:
         if self.cleanup and hasattr(self, 'name'):
             rm_dir(self.name)
 
+class EncryptedTemporaryFile:
+    # not implemented: next, readline, readlines, xreadlines, writelines
+
+    def __init__(self):
+        self.file = tempfile.TemporaryFile()
+        self.key = os.urandom(16)  # AES-128
+
+    def _crypt(self, offset, data):
+        offset_big = offset // 16
+        offset_small = offset % 16
+        iv = binascii.unhexlify("%032x" % offset_big)
+        cipher = AES(self.key, iv=iv)
+        cipher.process("\x00"*offset_small)
+        return cipher.process(data)
+
+    def close(self):
+        self.file.close()
+
+    def flush(self):
+        self.file.flush()
+
+    def seek(self, offset, whence=0):  # 0 = SEEK_SET
+        self.file.seek(offset, whence)
+
+    def tell(self):
+        offset = self.file.tell()
+        return offset
+
+    def read(self, size=-1):
+        index = self.file.tell()
+        ciphertext = self.file.read(size)
+        plaintext = self._crypt(index, ciphertext)
+        return plaintext
+
+    def write(self, plaintext):
+        index = self.file.tell()
+        ciphertext = self._crypt(index, plaintext)
+        self.file.write(ciphertext)
+
+    def truncate(self, newsize):
+        self.file.truncate(newsize)
+
+
 def make_dirs(dirname, mode=0777):
     """
     An idempotent version of os.makedirs().  If the dir already exists, do
-- 
2.45.2