lae's notebook

Using SCP to Provide a Public Upload Service

Half a year ago, I wrote a poorly detailed post about setting up a public upload site using SSH, which used the authorized_keys file to restrict the key to an rsync command with certain flags enabled and a specified destination directory. This was pretty poorly implemented so I ended up removing the upload script and private key to prevent abuse.

Some time ago I did look into finding solutions to prevent all the mishaps that could have happened with that method. I ended up writing a pass-through bash script that basically parses the command sent to the SSH server, checks that the input is sane and then executes it.

The Implementation

To start things off, here's the result:

#!/bin/bash -fue
set -- $SSH_ORIGINAL_COMMAND # passes the SSH command to ARGV
up='/home/johndoe/example.jp'
function error() {
    if [ -z "$*" ]; then details="request not permitted"; else details="$*"; fi
    echo -e "\aERROR: $details"
    exit
}
if [ "$1"  != 'scp' ]; then error; fi # checks to see if remote is using scp
if [ "$2" != "-t" ]; then error; fi # checks flags for local scp to retrieve a file
shift
shift
if [[ "$@" == '.' ]]; then error "destination not specified"; fi # checks that the command isn't scp -t .
if [[ "$@" == ../* ]] || [[ "$@" == ./* ]] || [[ "$@" == /* ]] || [[ "$@" == */* ]] || [[ "$@" == .. ]]; then
    error "destination traverses directories"
fi
dest=$up$@
if [[ -f "$dest" ]]; then error "file exists on server"; fi
exec scp -t "$dest"

We'll make this executable and place it at /home/johndoe/bin/restrict.sh. The following line should then be appended to /home/johndoe/.ssh/authorized_keys:

no-port-forwarding,no-X11-forwarding,no-pty,command="/home/johndoe/bin/restrict.sh" $PUBLICKEY

Of course, replace $PUBLICKEY with a valid public key (you should create a key pair that doesn't require a password to use, but that's up to you). Then, we basically use SCP to upload a file. For the restrict.sh script I provided above, you'll need to actually specify the destination file (because scp runs with '.' as the destination, and that could very well be the entire directory locally):

scp (-i $pathtoprivatekey) $srcfile johndoe@example.jp:$destfile

You can also write a script (like I have) or use an alias to make this simpler to perform on an everyday basis.

The Explanation

The comments in the script should explain what happens for the most part, but I'll reiterate here. I begin by defining the variables to be used throughout the script. $SSH_ORIGINAL_COMMAND is a variable provided by OpenSSH to the program specified by the command parameter in your authorize_keys file, which contains the command issued remotely. I use set here primarily to reduce the amount of code I have to write (it basically does the splitting of the variable for me). up is then defined to specify where files will be uploaded to (and in this case I use a directory accessible via HTTP).

The error() function is defined to return a message to the person sending a request to the server and then exit. I included an escaped alarm beep only because it seems that the 'E' disappears if I don't put anything other than an 'e' in front. (I tried removing it now and, for some reason, it works fine, so it might have just been a bug with an older SSH client from last year.)

The next two lines then check to see if the command being executed is scp with the t flag specified - this is the server counterpart to the scp command. After that's done, I use shift to remove the first two arguments.

$@ should then include the remaining arguments, which in most cases will be the destination file. Flags like the recursive flag r seem to get specified before t so this will prevent entire directories from being uploaded (it also prevents usage of the verbose flag, but you could add more logic to check). $@ is then matched against any patterns that would allow the destination to be any other directory than the one specified by the up variable defined earlier (it also matches root, but then again, you wouldn't use this script with root, would you?).

We then check to see if the destination file exists, and proceed to upload it if it does not.

Things to be Concerned About

There are a few serious issues with this approach, however. You'd want to implement a check for how much disk space is available on the server, and possibly prevent uploads if the disk is 90% full or so. The issue with this is that SCP doesn't pass any other metadata about the file being uploaded, so you can't check to see if uploading the file would cause the disk to become 100% full and cause server wide problems (of which you may not even be aware that this script could be the source of the issue).

You would also want to implement a flood check within the script. This could be pretty simple: you could have a data store that keeps track of files, dates of when the files were uploaded, and the size of files (after they were uploaded, of course), and then you could check to see how many files were uploaded in the last 30 minutes or count how much data was transferred in that time, and prevent any uploads for a limited amount of time. This could be an effective deterrent, but it won't stop floods with an extended duration (in other words, it's not difficult to write a while true loop that runs scp every minute on a randomly generated file). Since SCP doesn't even pass the IP of the uploader, you can't deny requests to certain IPs (well, I suppose you could parse netstat, but that doesn't seem like a reliable, effective method).*

In short, I would only use this to provide a service to friends and others I can trust not to abuse it. If either of those two problems have a solution I'm not aware about, I'm open to suggestions (and new knowledge, of course).

* (update 5 Mar) I just realised a week ago that SSH actually does pass the SSH_CONNECTION and SSH_CLIENT environment variables containing the sender's IP, so one should be able to track uploads via IP within the script easily. I'll see what I can do about the other issue.

First Dive Into Lua - A Battery Widget

So, it's come to the point where my laptop has unexpectedly turned off from a dead battery one too many times, so I decided to write a battery widget using Vicious for the window manager I'm using, Awesome. The configuration files are all written in Lua, and honestly I've never touched Lua or felt like programming in it since it looks so...confuzzling.

Nevertheless, I took a look at the Vicious and Naughty libraries, and some Lua documentation to get this up and running:

batmon = awful.widget.progressbar()
batmon:set_width(8)
batmon:set_vertical(true)
batmon:set_border_color("#3f3f3f")
batmon:set_color("#5f5f5f")
batmon_t = awful.tooltip({ objects = { batmon.widget },})
vicious.register(batmon, vicious.widgets.bat, function (widget, args)
	batmon_t:set_text(" State: " .. args[1] .. " | Charge: " .. args[2] .. "% | Remaining: " .. args[3])
	if args[2] <= 5 then
		naughty.notify({ text="Battery is low! " .. args[2] .. " percent remaining." })
	end
	return args[2]
end , 60, "BAT0")

What this basically does is create a progressbar widget with the Awful library, configure its settings, create a tooltip with detailed information, and registers the widget I created with Vicious. The Vicious portion of it uses the battery widget type and sets a timer to update it every 60 seconds, which updates the progressbar percentage and the tooltip. It also checks for a low battery, which for me pops up a little box at the upper right of my screen.

I'm probably not going to be touching Lua for a while again.

A Simple Bash Alarm Clock

Someone on IRC linked to a script called DEADLINE, which got me to thinking, a simple alarm clock script should be easy to concoct in bash if that's what the end goal is. I did a quick Google search but didn't find any simple solutions - they were all excessive in some way. So, here I ended up creating a bash one-liner in a few minutes to see it in practice and confirm I wasn't crazy:

sleep $(( $(date --date="7 pm Feb 23, 2012" +%s) - $(date +%s))); echo "It's been a year since you touched this, and the sky is dark! Lalala."

I could easily expand this to request a simple date and play an audio file:

#!/bin/bash
printf "What time are you setting this alarm for? "
read date
echo Okay! Will ring you on $(date --date="$date").
sleep $(( $(date --date="$date" +%s) - $(date +%s) ));
echo Wake up!
while true; do
  /usr/bin/mpg123 ~/alarm.mp3
  sleep 1
done

This can accept date inputs like "january 1 next year", "tomorrow", "23:00 today" and so forth. In fact, one could expand this script to test for valid dates, or replace the prompt with argument parsing for the same behaviour as "Deadline." I would also probably suggest adding nohup to the script, to relay its execution from the shell and into the background after the date has been inputted.

In fact, I may update this later when I have the time with a more robust (but still short and inexpensive) version.

There are a lot of things you can do with native UNIX utilities, and I'm not quite understanding why they aren't taken more advantage of.