1 /****************************************************************************** 2 * 3 * Copyright: 4 * Copyright © 2007 Daniel Keep. 5 * Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 6 * All rights reserved. 7 * 8 * License: 9 * Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 10 * See LICENSE_TANGO.txt for details. 11 * 12 * Version: 13 * Dec 2007: Initial release$(BR) 14 * May 2009: Inherit File 15 * 16 * Authors: Daniel Keep 17 * 18 * Credits: 19 * Thanks to John Reimer for helping test this module under Linux. 20 * 21 * 22 ******************************************************************************/ 23 24 module ocean.io.device.TempFile; 25 26 import ocean.transition; 27 import ocean.core.Verify; 28 29 import Path = ocean.io.Path; 30 import ocean.math.random.Kiss : Kiss; 31 import ocean.io.device.Device : Device; 32 import ocean.io.device.File; 33 import ocean.text.util.StringC; 34 35 import core.stdc.string : strlen; 36 import core.sys.posix.pwd : getpwnam; 37 import core.sys.posix.unistd : access, getuid, lseek, unlink, W_OK; 38 import core.sys.posix.sys.types : off_t; 39 import core.sys.posix.sys.stat : stat, stat_t; 40 import ocean.stdc.posix.fcntl : O_NOFOLLOW; 41 import core.sys.posix.stdlib : getenv; 42 43 /****************************************************************************** 44 * 45 * The TempFile class aims to provide a safe way of creating and destroying 46 * temporary files. The TempFile class will automatically close temporary 47 * files when the object is destroyed, so it is recommended that you make 48 * appropriate use of scoped destruction. 49 * 50 * Temporary files can be created with one of several styles, much like normal 51 * Files. TempFile styles have the following properties: 52 * 53 * $(UL 54 * $(LI $(B Transience): this determines whether the file should be destroyed 55 * as soon as it is closed (transient,) or continue to persist even after the 56 * application has terminated (permanent.)) 57 * ) 58 * 59 * Eventually, this will be expanded to give you greater control over the 60 * temporary file's properties. 61 * 62 * For the typical use-case (creating a file to temporarily store data too 63 * large to fit into memory,) the following is sufficient: 64 * 65 * ----- 66 * { 67 * scope temp = new TempFile; 68 * 69 * // Use temp as a normal conduit; it will be automatically closed when 70 * // it goes out of scope. 71 * } 72 * ----- 73 * 74 * Important: 75 * It is recommended that you $(I do not) use files created by this class to 76 * store sensitive information. There are several known issues with the 77 * current implementation that could allow an attacker to access the contents 78 * of these temporary files. 79 * 80 * Todo: Detail security properties and guarantees. 81 * 82 ******************************************************************************/ 83 84 class TempFile : File 85 { 86 /************************************************************************** 87 * 88 * This enumeration is used to control whether the temporary file should 89 * persist after the TempFile object has been destroyed. 90 * 91 **************************************************************************/ 92 93 enum Transience : ubyte 94 { 95 /** 96 * The temporary file should be destroyed along with the owner object. 97 */ 98 Transient, 99 /** 100 * The temporary file should persist after the object has been 101 * destroyed. 102 */ 103 Permanent 104 } 105 106 /************************************************************************** 107 * 108 * This structure is used to determine how the temporary files should be 109 * opened and used. 110 * 111 **************************************************************************/ 112 align(1) struct TempStyle 113 { 114 align(1): 115 //Visibility visibility; /// 116 Transience transience; /// 117 //Sensitivity sensitivity; /// 118 //Share share; /// 119 //Cache cache; /// 120 ubyte attempts = 10; /// 121 } 122 123 /** 124 * TempStyle for creating a transient temporary file that only the current 125 * user can access. 126 */ 127 static immutable TempStyle Transient = {Transience.Transient}; 128 /** 129 * TempStyle for creating a permanent temporary file that only the current 130 * user can access. 131 */ 132 static immutable TempStyle Permanent = {Transience.Permanent}; 133 134 // Path to the temporary file 135 private istring _path; 136 137 // TempStyle we've opened with 138 private TempStyle _style; 139 140 /// 141 this(TempStyle style = TempStyle.init) 142 { 143 open (style); 144 } 145 146 /// 147 this(istring prefix, TempStyle style = TempStyle.init) 148 { 149 open (prefix, style); 150 } 151 152 /************************************************************************** 153 * 154 * Indicates the style that this TempFile was created with. 155 * 156 **************************************************************************/ 157 TempStyle tempStyle() 158 { 159 return _style; 160 } 161 162 /* 163 * Creates a new temporary file with the given style. 164 */ 165 private void open (TempStyle style) 166 { 167 open (tempPath, style); 168 } 169 170 private void open (istring prefix, TempStyle style) 171 { 172 for( ubyte i=style.attempts; i--; ) 173 { 174 if( openTempFile(Path.join(prefix, randomName), style) ) 175 return; 176 } 177 178 error("could not create temporary file"); 179 } 180 181 private static immutable DEFAULT_LENGTH = 6; 182 private static immutable DEFAULT_PREFIX = ".tmp"; 183 184 // Use "~" to work around a bug in DMD where it elides empty constants 185 private static immutable DEFAULT_SUFFIX = "~"; 186 187 private static immutable JUNK_CHARS = 188 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 189 190 /*************************************************************************** 191 * 192 * Returns the path to the directory where temporary files will be 193 * created. The returned path is safe to mutate. 194 * 195 **************************************************************************/ 196 197 public static istring tempPath() 198 { 199 // Check for TMPDIR; failing that, use /tmp 200 char* ptr = getenv ("TMPDIR".ptr); 201 if (ptr is null) 202 return "/tmp/"; 203 else 204 return idup(ptr[0 .. strlen (ptr)]); 205 } 206 207 /* 208 * Creates a new temporary file at the given path, with the specified 209 * style. 210 */ 211 private bool openTempFile(cstring path, TempStyle style) 212 { 213 // Check suitability 214 { 215 mstring path_mut = Path.parse(path.dup).path; 216 assumeSafeAppend(path_mut); 217 auto parentz = StringC.toCString(path_mut); 218 219 // Make sure we have write access 220 if( access(parentz, W_OK) == -1 ) 221 error("do not have write access to temporary directory"); 222 223 // Get info on directory 224 stat_t sb; 225 if( stat(parentz, &sb) == -1 ) 226 error("could not stat temporary directory"); 227 228 // Get root's UID 229 auto pwe = getpwnam("root".ptr); 230 if( pwe is null ) error("could not get root's uid"); 231 auto root_uid = pwe.pw_uid; 232 233 // Make sure either we or root are the owner 234 if( !(sb.st_uid == root_uid || sb.st_uid == getuid) ) 235 error("temporary directory owned by neither root nor user"); 236 237 // Check to see if anyone other than us can write to the dir. 238 if( (sb.st_mode & Octal!("22")) != 0 && (sb.st_mode & Octal!("1000")) == 0 ) 239 error("sticky bit not set on world-writable directory"); 240 } 241 242 // Create file 243 { 244 Style filestyle = {Access.ReadWrite, Open.New, 245 Share.None, Cache.None}; 246 247 auto addflags = O_NOFOLLOW; 248 249 if (!super.open(path, filestyle, addflags, Octal!("600"))) 250 return false; 251 252 if( style.transience == Transience.Transient ) 253 { 254 // BUG TODO: check to make sure the path still points 255 // to the file we opened. Pity you can't unlink a file 256 // descriptor... 257 258 // NOTE: This should be an exception and not simply 259 // returning false, since this is a violation of our 260 // guarantees. 261 if( unlink((path ~ "\0").ptr) == -1 ) 262 error("could not remove transient file"); 263 } 264 265 _style = style; 266 267 return true; 268 } 269 } 270 271 /* 272 * Generates a new random file name, sans directory. 273 */ 274 private istring randomName(uint length=DEFAULT_LENGTH, 275 istring prefix=DEFAULT_PREFIX, 276 istring suffix=DEFAULT_SUFFIX) 277 { 278 import core.memory; 279 auto junk = new char[length]; 280 scope(exit) GC.free(junk.ptr); 281 282 foreach( ref c ; junk ) 283 { 284 verify(JUNK_CHARS.length < uint.max); 285 c = JUNK_CHARS[Kiss.instance.toInt(cast(uint) $)]; 286 } 287 288 return prefix ~ assumeUnique(junk) ~ suffix; 289 } 290 291 override void detach() 292 { 293 static assert( !is(Sensitivity) ); 294 super.detach(); 295 } 296 }