| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 | /****************************************************************************** * * copyright: Copyright © 2007 Daniel Keep. All rights reserved. * license: BSD style: $(LICENSE) * version: Dec 2007: Initial release$(BR) * May 2009: Inherit File * authors: Daniel Keep * credits: Thanks to John Reimer for helping test this module under * Linux. * ******************************************************************************/ module tango.io.device.TempFile; import Path = tango.io.Path; import tango.math.random.Kiss : Kiss; import tango.io.device.Device : Device; import tango.io.device.File; import tango.stdc.stringz : toStringz; import tango.core.Octal; /****************************************************************************** ******************************************************************************/ version( Win32 ) { import tango.sys.Common : DWORD, LONG, MAX_PATH, PCHAR, CP_UTF8; enum : DWORD { FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 } version( Win32SansUnicode ) { import tango.sys.Common : GetVersionExA, OSVERSIONINFO, FILE_FLAG_DELETE_ON_CLOSE, GetTempPathA; char[] GetTempPath() { auto len = GetTempPathA(0, null); if( len == 0 ) throw new Exception("could not obtain temporary path"); auto result = new char[len+1]; len = GetTempPathA(len+1, result.ptr); if( len == 0 ) throw new Exception("could not obtain temporary path"); return Path.standard(result[0..len]); } } else { import tango.sys.Common : WideCharToMultiByte, GetVersionExW, OSVERSIONINFO, FILE_FLAG_DELETE_ON_CLOSE, GetTempPathW; char[] GetTempPath() { auto len = GetTempPathW(0, null); if( len == 0 ) throw new Exception("could not obtain temporary path"); auto result = new wchar[len+1]; len = GetTempPathW(len+1, result.ptr); if( len == 0 ) throw new Exception("could not obtain temporary path"); auto dir = new char [len * 3]; auto i = WideCharToMultiByte (CP_UTF8, 0, result.ptr, len, cast(PCHAR) dir.ptr, dir.length, null, null); return Path.standard (dir[0..i]); } } // Determines if reparse points (aka: symlinks) are supported. Support // was introduced in Windows Vista. @property bool reparseSupported() { OSVERSIONINFO versionInfo = void; versionInfo.dwOSVersionInfoSize = versionInfo.sizeof; void e(){throw new Exception("could not determine Windows version");} version( Win32SansUnicode ) { if( !GetVersionExA(&versionInfo) ) e(); } else { if( !GetVersionExW(&versionInfo) ) e(); } return (versionInfo.dwMajorVersion >= 6); } } else version( Posix ) { import tango.stdc.posix.pwd : getpwnam; import tango.stdc.posix.unistd : access, getuid, lseek, unlink, W_OK; import tango.stdc.posix.sys.types : off_t; import tango.stdc.posix.sys.stat : stat, stat_t; import tango.sys.consts.fcntl : O_NOFOLLOW; import tango.stdc.posix.stdlib : getenv; import tango.stdc.string : strlen; } /****************************************************************************** * * The TempFile class aims to provide a safe way of creating and destroying * temporary files. The TempFile class will automatically close temporary * files when the object is destroyed, so it is recommended that you make * appropriate use of scoped destruction. * * Temporary files can be created with one of several styles, much like normal * Files. TempFile styles have the following properties: * * $(UL * $(LI $(B Transience): this determines whether the file should be destroyed * as soon as it is closed (transient,) or continue to persist even after the * application has terminated (permanent.)) * ) * * Eventually, this will be expanded to give you greater control over the * temporary file's properties. * * For the typical use-case (creating a file to temporarily store data too * large to fit into memory,) the following is sufficient: * * ----- * { * scope temp = new TempFile; * * // Use temp as a normal conduit; it will be automatically closed when * // it goes out of scope. * } * ----- * * Important: * It is recommended that you $(I do not) use files created by this class to * store sensitive information. There are several known issues with the * current implementation that could allow an attacker to access the contents * of these temporary files. * * Todo: Detail security properties and guarantees. * ******************************************************************************/ class TempFile : File { /+enum Visibility : ubyte { /** * The temporary file will have read and write access to it restricted * to the current user. */ User, /** * The temporary file will have read and write access available to any * user on the system. */ World }+/ /************************************************************************** * * This enumeration is used to control whether the temporary file should * persist after the TempFile object has been destroyed. * **************************************************************************/ enum Transience : ubyte { /** * The temporary file should be destroyed along with the owner object. */ Transient, /** * The temporary file should persist after the object has been * destroyed. */ Permanent } /+enum Sensitivity : ubyte { /** * Transient files will be truncated to zero length immediately * before closure to prevent casual filesystem inspection to recover * their contents. * * No additional action is taken on permanent files. */ None, /** * Transient files will be zeroed-out before truncation, to mask their * contents from more thorough filesystem inspection. * * This option is not compatible with permanent files. */ Low /+ /** * Transient files will be overwritten first with zeroes, then with * ones, and then with a random 32- or 64-bit pattern (dependant on * which is most efficient.) The file will then be truncated. * * This option is not compatible with permanent files. */ Medium +/ }+/ /************************************************************************** * * This structure is used to determine how the temporary files should be * opened and used. * **************************************************************************/ align(1) struct TempStyle { //Visibility visibility; /// Transience transience; /// //Sensitivity sensitivity; /// //Share share; /// //Cache cache; /// ubyte attempts = 10; /// } /** * TempStyle for creating a transient temporary file that only the current * user can access. */ static __gshared const TempStyle Transient = {Transience.Transient}; /** * TempStyle for creating a permanent temporary file that only the current * user can access. */ static __gshared const TempStyle Permanent = {Transience.Permanent}; // Path to the temporary file private char[] _path; // TempStyle we've opened with private TempStyle _style; /// this(TempStyle style = TempStyle.init) { open (style); } /// this(const(char)[] prefix, TempStyle style = TempStyle.init) { open (prefix, style); } /************************************************************************** * * Indicates the style that this TempFile was created with. * **************************************************************************/ TempStyle tempStyle() { return _style; } /* * Creates a new temporary file with the given style. */ private void open (TempStyle style) { open (tempPath(), style); } private void open (const(char)[] prefix, TempStyle style) { for( ubyte i=style.attempts; i--; ) { if( openTempFile(Path.join(prefix, randomName()), style) ) return; } error("could not create temporary file"); } version( Win32 ) { private static enum DEFAULT_LENGTH = 6; private static enum DEFAULT_PREFIX = "~t"; private static enum DEFAULT_SUFFIX = ".tmp"; private static enum JUNK_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"; /********************************************************************** * * Returns the path to the directory where temporary files will be * created. The returned path is safe to mutate. * **********************************************************************/ public static char[] tempPath() { return GetTempPath(); } /* * Creates a new temporary file at the given path, with the specified * style. */ private bool openTempFile(const(char)[] path, TempStyle style) { // TODO: Check permissions directly and throw an exception; // otherwise, we could spin trying to make a file when it's // actually not possible. Style filestyle = {Access.ReadWrite, Open.New, Share.None, Cache.None}; DWORD attr; // Set up flags attr = reparseSupported ? FILE_FLAG_OPEN_REPARSE_POINT : 0; if( style.transience == Transience.Transient ) attr |= FILE_FLAG_DELETE_ON_CLOSE; if (!super.open (path, filestyle, attr)) return false; _style = style; return true; } } else version( Posix ) { private static enum DEFAULT_LENGTH = 6; private static enum DEFAULT_PREFIX = ".tmp"; // Use "~" to work around a bug in DMD where it elides empty constants private static enum DEFAULT_SUFFIX = "~"; private static enum JUNK_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789"; /********************************************************************** * * Returns the path to the directory where temporary files will be * created. The returned path is safe to mutate. * **********************************************************************/ public static const(char)[] tempPath() { // Check for TMPDIR; failing that, use /tmp char* ptr = getenv ("TMPDIR"); if (ptr is null) return "/tmp/"; else return ptr[0 .. strlen (ptr)].dup; } /* * Creates a new temporary file at the given path, with the specified * style. */ private bool openTempFile(const(char)[] path, TempStyle style) { // Check suitability { auto parentz = toStringz(Path.parse(path).path); // Make sure we have write access if( access(parentz, W_OK) == -1 ) error("do not have write access to temporary directory"); // Get info on directory stat_t sb; if( stat(parentz, &sb) == -1 ) error("could not stat temporary directory"); // Get root's UID auto pwe = getpwnam("root"); if( pwe is null ) error("could not get root's uid"); auto root_uid = pwe.pw_uid; // Make sure either we or root are the owner if( !(sb.st_uid == root_uid || sb.st_uid == getuid()) ) error("temporary directory owned by neither root nor user"); // Check to see if anyone other than us can write to the dir. if( (sb.st_mode & octal!22) != 0 && (sb.st_mode & octal!1000) == 0 ) error("sticky bit not set on world-writable directory"); } // Create file { Style filestyle = {Access.ReadWrite, Open.New, Share.None, Cache.None}; auto addflags = O_NOFOLLOW; if (!super.open(path, filestyle, addflags, octal!600)) return false; if( style.transience == Transience.Transient ) { // BUG TODO: check to make sure the path still points // to the file we opened. Pity you can't unlink a file // descriptor... // NOTE: This should be an exception and not simply // returning false, since this is a violation of our // guarantees. if( unlink(toStringz(path)) == -1 ) error("could not remove transient file"); } _style = style; return true; } } } else { static assert(false, "Unsupported platform"); } /* * Generates a new random file name, sans directory. */ private char[] randomName(size_t length=DEFAULT_LENGTH, const(char)[] prefix=DEFAULT_PREFIX, const(char)[] suffix=DEFAULT_SUFFIX) { auto junk = new char[length]; scope(exit) delete junk; foreach( ref c ; junk ) c = JUNK_CHARS[Kiss.instance.toInt(cast(uint)$)]; return prefix~junk~suffix; } override void detach() { static assert( !is(Sensitivity) ); super.detach(); } } version( TempFile_SelfTest ): import tango.io.Console : Cin; import tango.io.Stdout : Stdout; void main() { Stdout(r" Please ensure that the transient file no longer exists once the TempFile object is destroyed, and that the permanent file does. You should also check the following on both: * the file should be owned by you, * the owner should have read and write permissions, * no other permissions should be set on the file. For POSIX systems: * the temp directory should be owned by either root or you, * if anyone other than root or you can write to it, the sticky bit should be set, * if the directory is writable by anyone other than root or the user, and the sticky bit is *not* set, then creating the temporary file should fail. You might want to delete the permanent one afterwards, too. :)") .newline; Stdout.formatln("Creating a transient file:"); { scope tempFile = new TempFile(/*TempFile.UserPermanent*/); Stdout.formatln(" .. path: {}", tempFile); tempFile.write("Transient temp file."); char[] buffer = new char[1023]; tempFile.seek(0); buffer = buffer[0..tempFile.read(buffer)]; Stdout.formatln(" .. contents: \"{}\"", buffer); Stdout(" .. press Enter to destroy TempFile object.").newline; Cin.copyln(); } Stdout.newline; Stdout.formatln("Creating a permanent file:"); { scope tempFile = new TempFile(TempFile.Permanent); Stdout.formatln(" .. path: {}", tempFile); tempFile.write("Permanent temp file."); char[] buffer = new char[1023]; tempFile.seek(0); buffer = buffer[0..tempFile.read(buffer)]; Stdout.formatln(" .. contents: \"{}\"", buffer); Stdout(" .. press Enter to destroy TempFile object.").flush; Cin.copyln(); } Stdout("\nDone.").newline; } |