proxy.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Sam Tuke, Robin Appelman
  6. * @copyright 2012 Sam Tuke samtuke@owncloud.com, Robin Appelman
  7. * icewind1991@gmail.com
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11. * License as published by the Free Software Foundation; either
  12. * version 3 of the License, or any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public
  20. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. /**
  24. * @brief Encryption proxy which handles filesystem operations before and after
  25. * execution and encrypts, and handles keyfiles accordingly. Used for
  26. * webui.
  27. */
  28. namespace OCA\Encryption;
  29. /**
  30. * Class Proxy
  31. * @package OCA\Encryption
  32. */
  33. class Proxy extends \OC_FileProxy {
  34. private static $blackList = null; //mimetypes blacklisted from encryption
  35. private static $enableEncryption = null;
  36. /**
  37. * Check if a file requires encryption
  38. * @param string $path
  39. * @return bool
  40. *
  41. * Tests if server side encryption is enabled, and file is allowed by blacklists
  42. */
  43. private static function shouldEncrypt($path) {
  44. if (is_null(self::$enableEncryption)) {
  45. if (
  46. \OCP\Config::getAppValue('files_encryption', 'enable_encryption', 'true') === 'true'
  47. && Crypt::mode() === 'server'
  48. ) {
  49. self::$enableEncryption = true;
  50. } else {
  51. self::$enableEncryption = false;
  52. }
  53. }
  54. if (!self::$enableEncryption) {
  55. return false;
  56. }
  57. if (is_null(self::$blackList)) {
  58. self::$blackList = explode(',', \OCP\Config::getAppValue('files_encryption', 'type_blacklist', ''));
  59. }
  60. if (Crypt::isCatfileContent($path)) {
  61. return true;
  62. }
  63. $extension = substr($path, strrpos($path, '.') + 1);
  64. if (array_search($extension, self::$blackList) === false) {
  65. return true;
  66. }
  67. return false;
  68. }
  69. /**
  70. * @param $path
  71. * @param $data
  72. * @return bool
  73. */
  74. public function preFile_put_contents($path, &$data) {
  75. if (self::shouldEncrypt($path)) {
  76. if (!is_resource($data)) {
  77. // get root view
  78. $view = new \OC_FilesystemView('/');
  79. // get relative path
  80. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  81. if (!isset($relativePath)) {
  82. return true;
  83. }
  84. $handle = fopen('crypt://' . $path . '.etmp', 'w');
  85. if (is_resource($handle)) {
  86. // write data to stream
  87. fwrite($handle, $data);
  88. // close stream
  89. fclose($handle);
  90. // disable encryption proxy to prevent recursive calls
  91. $proxyStatus = \OC_FileProxy::$enabled;
  92. \OC_FileProxy::$enabled = false;
  93. // get encrypted content
  94. $data = $view->file_get_contents($path . '.etmp');
  95. // remove our temp file
  96. $view->unlink($path . '.etmp');
  97. // re-enable proxy - our work is done
  98. \OC_FileProxy::$enabled = $proxyStatus;
  99. }
  100. }
  101. }
  102. return true;
  103. }
  104. /**
  105. * @param string $path Path of file from which has been read
  106. * @param string $data Data that has been read from file
  107. */
  108. public function postFile_get_contents($path, $data) {
  109. $plainData = null;
  110. $view = new \OC_FilesystemView('/');
  111. // init session
  112. $session = new \OCA\Encryption\Session($view);
  113. // If data is a catfile
  114. if (
  115. Crypt::mode() === 'server'
  116. && Crypt::isCatfileContent($data)
  117. ) {
  118. $handle = fopen('crypt://' . $path, 'r');
  119. if (is_resource($handle)) {
  120. while (($plainDataChunk = fgets($handle, 8192)) !== false) {
  121. $plainData .= $plainDataChunk;
  122. }
  123. }
  124. } elseif (
  125. Crypt::mode() == 'server'
  126. && \OC::$session->exists('legacyenckey')
  127. && Crypt::isEncryptedMeta($path)
  128. ) {
  129. // Disable encryption proxy to prevent recursive calls
  130. $proxyStatus = \OC_FileProxy::$enabled;
  131. \OC_FileProxy::$enabled = false;
  132. $plainData = Crypt::legacyBlockDecrypt($data, $session->getLegacyKey());
  133. \OC_FileProxy::$enabled = $proxyStatus;
  134. }
  135. if (!isset($plainData)) {
  136. $plainData = $data;
  137. }
  138. return $plainData;
  139. }
  140. /**
  141. * @brief When a file is deleted, remove its keyfile also
  142. */
  143. public function preUnlink($path) {
  144. // let the trashbin handle this
  145. if (\OCP\App::isEnabled('files_trashbin')) {
  146. return true;
  147. }
  148. // Disable encryption proxy to prevent recursive calls
  149. $proxyStatus = \OC_FileProxy::$enabled;
  150. \OC_FileProxy::$enabled = false;
  151. $view = new \OC_FilesystemView('/');
  152. $userId = \OCP\USER::getUser();
  153. $util = new Util($view, $userId);
  154. // get relative path
  155. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  156. list($owner, $ownerPath) = $util->getUidAndFilename($relativePath);
  157. // Delete keyfile & shareKey so it isn't orphaned
  158. if (!Keymanager::deleteFileKey($view, $owner, $ownerPath)) {
  159. \OCP\Util::writeLog('Encryption library',
  160. 'Keyfile or shareKey could not be deleted for file "' . $ownerPath . '"', \OCP\Util::ERROR);
  161. }
  162. Keymanager::delAllShareKeys($view, $owner, $ownerPath);
  163. \OC_FileProxy::$enabled = $proxyStatus;
  164. // If we don't return true then file delete will fail; better
  165. // to leave orphaned keyfiles than to disallow file deletion
  166. return true;
  167. }
  168. /**
  169. * @param $path
  170. * @return bool
  171. */
  172. public function postTouch($path) {
  173. $this->handleFile($path);
  174. return true;
  175. }
  176. /**
  177. * @param $path
  178. * @param $result
  179. * @return resource
  180. */
  181. public function postFopen($path, &$result) {
  182. $path = \OC\Files\Filesystem::normalizePath($path);
  183. if (!$result) {
  184. return $result;
  185. }
  186. // split the path parts
  187. $pathParts = explode('/', $path);
  188. // get relative path
  189. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  190. // FIXME: handling for /userId/cache used by webdav for chunking. The cache chunks are NOT encrypted
  191. if (isset($pathParts[2]) && $pathParts[2] === 'cache') {
  192. return $result;
  193. }
  194. // Disable encryption proxy to prevent recursive calls
  195. $proxyStatus = \OC_FileProxy::$enabled;
  196. \OC_FileProxy::$enabled = false;
  197. $meta = stream_get_meta_data($result);
  198. $view = new \OC_FilesystemView('');
  199. $util = new Util($view, \OCP\USER::getUser());
  200. // If file is already encrypted, decrypt using crypto protocol
  201. if (
  202. Crypt::mode() === 'server'
  203. && $util->isEncryptedPath($path)
  204. ) {
  205. // Close the original encrypted file
  206. fclose($result);
  207. // Open the file using the crypto stream wrapper
  208. // protocol and let it do the decryption work instead
  209. $result = fopen('crypt://' . $path, $meta['mode']);
  210. } elseif (
  211. self::shouldEncrypt($path)
  212. and $meta ['mode'] !== 'r'
  213. and $meta['mode'] !== 'rb'
  214. ) {
  215. $result = fopen('crypt://' . $path, $meta['mode']);
  216. }
  217. // Re-enable the proxy
  218. \OC_FileProxy::$enabled = $proxyStatus;
  219. return $result;
  220. }
  221. /**
  222. * @param $path
  223. * @param $data
  224. * @return array
  225. */
  226. public function postGetFileInfo($path, $data) {
  227. // if path is a folder do nothing
  228. if (is_array($data) && array_key_exists('size', $data)) {
  229. // Disable encryption proxy to prevent recursive calls
  230. $proxyStatus = \OC_FileProxy::$enabled;
  231. \OC_FileProxy::$enabled = false;
  232. // get file size
  233. $data['size'] = self::postFileSize($path, $data['size']);
  234. // Re-enable the proxy
  235. \OC_FileProxy::$enabled = $proxyStatus;
  236. }
  237. return $data;
  238. }
  239. /**
  240. * @param $path
  241. * @param $size
  242. * @return bool
  243. */
  244. public function postFileSize($path, $size) {
  245. $view = new \OC_FilesystemView('/');
  246. // if path is a folder do nothing
  247. if ($view->is_dir($path)) {
  248. return $size;
  249. }
  250. // get relative path
  251. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  252. // if path is empty we cannot resolve anything
  253. if (empty($relativePath)) {
  254. return $size;
  255. }
  256. $fileInfo = false;
  257. // get file info from database/cache if not .part file
  258. if (!Keymanager::isPartialFilePath($path)) {
  259. $fileInfo = $view->getFileInfo($path);
  260. }
  261. // if file is encrypted return real file size
  262. if (is_array($fileInfo) && $fileInfo['encrypted'] === true) {
  263. $size = $fileInfo['unencrypted_size'];
  264. } else {
  265. // self healing if file was removed from file cache
  266. if (!is_array($fileInfo)) {
  267. $fileInfo = array();
  268. }
  269. $userId = \OCP\User::getUser();
  270. $util = new Util($view, $userId);
  271. $fixSize = $util->getFileSize($path);
  272. if ($fixSize > 0) {
  273. $size = $fixSize;
  274. $fileInfo['encrypted'] = true;
  275. $fileInfo['unencrypted_size'] = $size;
  276. // put file info if not .part file
  277. if (!Keymanager::isPartialFilePath($relativePath)) {
  278. $view->putFileInfo($path, $fileInfo);
  279. }
  280. }
  281. }
  282. return $size;
  283. }
  284. /**
  285. * @param $path
  286. */
  287. public function handleFile($path) {
  288. // Disable encryption proxy to prevent recursive calls
  289. $proxyStatus = \OC_FileProxy::$enabled;
  290. \OC_FileProxy::$enabled = false;
  291. $view = new \OC_FilesystemView('/');
  292. $session = new \OCA\Encryption\Session($view);
  293. $userId = \OCP\User::getUser();
  294. $util = new Util($view, $userId);
  295. // split the path parts
  296. $pathParts = explode('/', $path);
  297. // get relative path
  298. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  299. // only if file is on 'files' folder fix file size and sharing
  300. if (isset($pathParts[2]) && $pathParts[2] === 'files' && $util->fixFileSize($path)) {
  301. // get sharing app state
  302. $sharingEnabled = \OCP\Share::isEnabled();
  303. // get users
  304. $usersSharing = $util->getSharingUsersArray($sharingEnabled, $relativePath);
  305. // update sharing-keys
  306. $util->setSharedFileKeyfiles($session, $usersSharing, $relativePath);
  307. }
  308. \OC_FileProxy::$enabled = $proxyStatus;
  309. }
  310. }