How can I allow users to download files via a symlink?

Avatar
  • Answered
At my previous host I had set up a special script for security purposes where certain files where stored in a protected directory with coded names. When a user was confirmed to be a valid user when they attempted to download a file, a temporary symlink was created pointing to the protected file. Thus, if the user then decided to share this link publicly, it wouldn't work since the symlink would be deleted later, and no users would know what the actual physical filename was on the server so the direct link also could not be shared.

I read about this technique elsewhere on the internet at the time, while searching for a way to allow users to download files via normal http download, but not allow them to easily share the download link publicly, and without depending on php "readfile" which can hog too much memory when serving large files.

How it works is that all the secured files are in a separate folder and are each named with a long bunch of random numbers and letters instead of a real filename, so users couldn't just guess at their location and name. A database entry stores what the original filename was for each of them. When a user requests a download, a temporary directory is created with a random name, and a symlink is placed inside. The symlink uses the original filename and points to the physical file in the other folder, and this symlink is then served up to the user. This way, they see a download link like /downloads/19houn0/RealFileName.zip but since this symlink and temp folder are deleted later, it won't do any good to share the link publicly, and they have no idea what the actual name and location of the file they are getting is.

On my old web host, this system worked fine, however, it doesn't seem to work now that I've migrated to InMotionHosting - I get a "Forbidden: You don't have permission to access..." error when trying to download the file via the symlink as a public user, via the script or directly typing in the generated link, even though I've verified the folder and symlink have full permissions set.

For an example, I've left the following symlink available as a test, and if I try directing my browser to: http://www.crazyvikingstudios.com/beta/download/85yhizj/VolgarrManual.zip then I get the Forbidden error. I can download it via the file manager in cPanel though, so the symlink seems to be set up correctly. I can also publicly download the file itself, if I happen to know its name, which in this case is http://www.crazyvikingstudios.com/beta/files/2_a6d56e92e56ab7e02690fea660c183b1 so I know users are able to download the file being linked to, and if I go to just http://www.crazyvikingstudios.com/beta/download/85klsjluq/ I can download the .png I dropped in there as a test just fine, I just can't download the file via the symlink.

From searching online, it seems the problem is that apache isn't set up to allow public users to follow symlinks on my server, but I can't see any way of configuring it otherwise. I'd really like to get this system working again, its very useful for distributing beta builds to registered beta testers securely and without them having to hassle with special download managers or gobbling up memory trying to use php's readfile function.

Here is the actual script that runs when a registered user clicks on a download link (this is of course after they have had their user name and password verified):

$doc_root_path = $_SERVER["DOCUMENT_ROOT"];
$safedir = $doc_root_path . '/beta/' . $upload_dir . '/';
$downloadURL = 'beta/download/';
$physfilename = $attachment['physical_filename'];
$realfilename = $attachment['real_filename'];
// Create new symlink
$letters = 'abcdefghijklmnopqrstuvwxyz';
srand((double) microtime() * 1000000);
$symlinkstring = $attachment['attach_id'];
for ($i = 1; $i <= rand(4,12); $i++)
{
$q = rand(1,26);
$symlinkstring = $symlinkstring . $letters[$q];
}
mkdir($downloaddir . $symlinkstring, 0777);
symlink($safedir . $physfilename, $downloaddir . $symlinkstring . "/" . $realfilename);
Header("Location: /" . $downloadURL . $symlinkstring . "/" . $realfilename);

An explanation of this technique can be found at http://stackoverflow.com/a/3010547/4143
Avatar
JeffMa
Hello, As 777 file permissions are extremely dangerous due to providing write access on the file or directory to absolutely anyone, most content will be blocked when accessed on the server to protect you. I recommend you adjust your code to create permissions 755 for directories and 644 for files in which your script should then work. Best regards, JeffMa
Avatar
Tim S.
Hi adabarjhai,

Thanks for your question about symlinks and performance. The short answer is no it should not impact performance.

I hope this helps!

Thanks!

Tim S