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;
}
|