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 }