Browsing articles tagged with " proof of concept" I’m in your IRC, SMSing your SMSes

This script is a proof-of-concept that I wanted to try more than anything else. Lightly modelled on bitlbee, it pretends to be an IRC server, and accepts/sends SMSes via a gateway (or a gateway app on your Android phone). It’s very much not a finished product and is known to have memory leaks. It also doesn’t clean up after it’s threads.

This script requires threads, threads::shared and IO::Socket.

Config-wise, it’s fairly straightforward – it’s all done in lines 6-21 of the script source. Set $IRCport and $IRCbind to the IP and TCP port you want the server to listen on. Set $IRCpassword to be the password that the IRC client must submit to connect.

The only part that gets tricky is interacting with the gateway. $SMScommand should be set to a script to pass the SMS off to the gateway for delivery. You can use the variables %TO% and %TEXT% in the script path as parameters – these will be replaced with the recipient and the text of the message respectively before the script is called. This could be a PHP script (“/usr/bin/php send-sms.php %TO% ‘%TEXT%’”), a URL using curl or wget (“/usr/bin/wget http://localhost/cgi-bin/sms.cgi?to=%TO%&text=%TEXT%”), an email address using sendmail or similar (“echo %TEXT% | /usr/sbin/sendmail”) or anything else you might want to use.

Likewise, receiving SMSes can be a bit tricky. $SMSreceiveKey needs to be set to a key that will be used to ensure that the SMSes are received from the real gateway. SMSes are accepted from the gateway by connecting to the IRC server socket and sending nothing more than:

DELIVER sms-receive-key 1234567890 :SMS text goes here.\r\n

The connection does not have be registered as an IRC client or authenticated to the server. It’s important to note that the IP and port you have set earlier in the configuration must be accessible by whatever is delivering the SMSes as well, or it will not be able to connect. In a lot of cases, SMS gateway services or gateway apps running on your phone will only support email forwarding or HTTP delivery of incoming texts. In either of these cases, I suggest creating a script to deliver the SMS for you. In the former case, a Perl script that gets called by a pipe from the mail server which connects to the server’s socket and deliver’s the SMS would be pretty straightforward. Likewise, a PHP script on a webserver could be configured to accept an SMS by GET/POST parameters and connect to the socket to deliver it.

Use-wise, it’s fairly straightforward. You can type ‘help’ in #sms after connecting for a short summary of this information. When you receive an SMS, the phone number it is received from will join #sms and address the text message to your attention:

* 1234567890 has joined #sms.
<1234567890> your-nickname: Hello there.

You can reply in kind:

<your-nickname> 1234567890: Hiya.

You can also send a text message to a number not presently in the channel the same way – just address the message at the number and SMSbot will ensure it’s delivered for you.

You can also message people by opening a private chat with them – either use ‘/query 123456790′ or double-click their name on the userlist of most IRC clients to open a private chat. Send your message, and replies will automatically be sent back to you in private message. To switch back to receiving their messages in #sms, just message them from the channel again and their replies will go there instead.

Finally, you can use the channel’s userlist as a sort of contact list by adding and removing users from it. When you receive an SMS from someone not already in the channel, they will join. You can also add them to the channel without sending them a message:

<your-nickname> add 1234567890
* 1234567890 has joined #sms.

Likewise, you can remove someone from the channel whether they joined because you added them or because they sent you a message:

<your-nickname> remove 1234567890
* 1234567890 has left #sms.

If you remove someone from the channel, they will join again the next time they send you a message.

Script Source

use IO::Socket;
use threads;
use threads::shared;

##### Config #####

# IRCd settings. At least change the password.
my $IRCport = "21337";
my $IRCbind = "";
my $IRCpassword = "irc4sms";

# Shell command to send SMSes via gateway.
# Consider using wget or sendmail!
# Variables! %TO% and %TEXT%
my $SMScommand = "/usr/bin/wget http://localhost/cgi-bin/send-sms.cgi?to=%TO%&text=%TEXT%";

# Key to receive inbound SMSes. Change this.
my $SMSreceiveKey = "1rc45m5!";

##### End Config ######

$version = "0.2";
$crlf = "\r\n";

my @msgQueue;
my %joined;


my $listen_socket = IO::Socket::INET->new(
	LocalPort => $IRCport,
	LocalAddr => $IRCbind,
	Listen => 10,
	Proto => 'tcp',
	Reuse => 1
die "Can't bind local socket: $!\n" unless $listen_socket;

while(my $connection = $listen_socket->accept){
	my $child = threads->create("read_data", $connection);

sub deliver_data {
	my($socket, $nickname) = @_;
		foreach my $message(@msgQueue) {
				my ($sender, $text) = $message =~ /^(.+?)\:(.+)$/;
				if($sender && $text){
						if($joined{$sender} < 1){
								$joined{$sender} = 1;
								print $socket ':'.$sender.' JOIN #sms'.$crlf;
						if($joined{$sender} == 1){ print $socket ':'.$sender.' PRIVMSG #sms :'.$nickname.': '.$text.$crlf; }
						if($joined{$sender} > 1){ print $socket ':'.$sender.' PRIVMSG '.$nickname.' :'.$text.$crlf; }
		@msgQueue = ();

sub read_data {
	my($socket) = @_;
	my $registered = 0;

	print $socket ':irc4sms.local NOTICE AUTH :irc4sms ready. Login or deliver an SMS.'.$crlf;
		$data = $_;
		print $data;
		$data =~ s/[\r\n]//ig;

		if($data =~ /^PING (.+)$/){
			print $socket ':irc4sms.local PONG irc4sms.local :'.$1.$crlf;

		if($registered < 3){
			if($data =~ /^DELIVER (.+?) (.+?) \:(.+)$/ && $1 eq $SMSreceiveKey){
				$sender = $2;
				$text = $3;
				$sender =~ s/^\+//;
				$msgQueue[++$#msgQueue] = $sender.":".$text;
				print $socket 'MESSAGE OK ('.$sender.'): '.$text.' - Queued for delivery.'.$crlf;
			}elsif($data =~ /^USER (.+?) .+ (?:\:|)(.+)$/){
				$username = $1;
				$realname = $3;
			}elsif($data =~ /^NICK (.+)$/){
				$nickname = $1;
				print $socket ':irc4sms.local 464 '.$nickname.' :P assword required.'.$crlf;
			}elsif($data =~ /^PASS (.+)$/){
				if($1 eq $IRCpassword){
					print $socket ':irc4sms.local 464 '.$nickname.' :Invalid password.'.$crlf;

			if($registered == 3){
				print $socket ':irc4sms.local 001 '.$nickname.' :Welcome'.$crlf;
				print $socket ':irc4sms.local 002 '.$nickname.' :Host irc4sms.local is running irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 003 '.$nickname.' :irc4sms ('.$crlf;
				print $socket ':irc4sms.local 004 '.$nickname.' irc4sms.local abiswRo ntC'.$crlf;
				print $socket ':irc4sms.local 005 '.$nickname.' PREFIX=(ohv)@%+ CHANTYPES=# CHANMODES=,,,ntC NICKLEN=25 CHANNELLEN=25 NETWORK=irc4sms SAFELIST CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server'.$crlf;
				print $socket ':irc4sms.local 375 '.$nickname.' :- irc4sms.local MOTD:'.$crlf;
				print $socket ':irc4sms.local 372 '.$nickname.' :- Welcome to irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 376 '.$nickname.' :End of MOTD'.$crlf;
				print $socket ':'.$nickname.'!'.$username.'@irc4sms.local MODE cm :+sR'.$crlf;
				print $socket ':'.$nickname.'!'.$username.'@irc4sms.local JOIN :#sms'.$crlf;
				print $socket ':irc4sms.local MODE #sms +Ct'.$crlf;
				print $socket ':irc4sms.local 353 '.$nickname.' = #sms :@'.$nickname.' @SMSbot'.$crlf;
				print $socket ':irc4sms.local 366 '.$nickname.' #sms :End of /NAMES list'.$crlf;
				print $socket ':irc4sms.local 332 '.$nickname.' #sms :irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 333 '.$nickname.' #sms SMSbot!SMSbot@irc4sms.local '.(time - 60).$crlf;
				$joined{$nickname} = 1;
				$joined{'SMSbot'} = 1;
				my $delivery = threads->create("deliver_data", $socket, $nickname);
			if($data =~ /^MODE/){
				print $socket ':irc4sms.local 324 '.$nickname.' #sms +Ct'.$crlf;
			}elsif($data =~ /^WHO/){
				print $socket ':irc4sms.local 352 '.$nickname.' #sms '.$username.' irc4sms.local irc4sms.local '.$nickname.' H@ :0 '.$realname.$crlf;
				print $socket ':irc4sms.local 352 '.$nickname.' #sms irc4sms irc4sms.local irc4sms.local SMSbot H@ :0 irc4sms '.$version.$crlf;
			}elsif($data =~ /^PRIVMSG \#sms \:(.+?)(?:\:|\,) (.+)$/){
				if($joined{$1} > 0){
					$joined{$1} = 1;
				$to = $1;
				$text = $2;
				$cmd = $SMScommand;
				$cmd =~ s/\%TO\%/$to/g;
				$cmd =~ s/\%TEXT\%/$text/g;
			}elsif($data =~ /^PRIVMSG ([a-zA-Z0-9\+]+?) \:(.+?)$/){
				if($joined{$1} > 0){
					$joined{$1} = 2;
				$to = $1;
				$text = $2;
				$cmd = $SMScommand;
				$cmd =~ s/\%TO\%/$to/g;
				$cmd =~ s/\%TEXT\%/$text/g;
			}elsif($data =~ /^PRIVMSG \#sms :( .+?)$/){
				$command = $1;
				if($command =~ /^add (.+)/ && !$joined{$1} ){
					$joined{$1} = 1;
					print $socket ':'.$1.' JOIN #sms'.$crlf;
				}elsif($command =~ /^remove (.+?)$/ && ($joined{$1})){
					$joined{$1} = '';
					print $socket ':'.$1.' QUIT :Leaving...'.$crlf;
				}elsif($command =~ /^help/){
					print $socket ':SMSbot PRIVMSG #sms :Commands: add 442392123456, remove 442392123456'.$crlf;
					print $socket ':SMSbot PRIVMSG #sms :If you /msg a contact, responses will automatically come as a /msg'.$crlf;
					print $socket ':SMSbot PRIVMSG #sms :Otherwise, address the contact in this channel, and their replies will come here'.$crlf;
					print $socket ':SMSbot PRIVMSG #sms :I don\'t recognise that as a valid command. Sorry. Try "help".'.$crlf;

WebDAV is a … pain

[Titled edited ;P]

As a concept – WebDAV is great. File shares over HTTP – no fussy protocols to deal with, security and encryption built right in.

Problems. You want to use Apache as a WebDAV server with mod_dav. Theroetically, this is fine. In my circumstances, I wanted to use it for a few specific people to reach their /home/username folders, so I thought using Apache I could just export those directories as DAV shares, using LDAP authentication. I hit several problems with this.

First, permissions. Apache obviously runs as ‘nobody’ or ‘apache’ or ‘httpd’ depending on your system. This user doesn’t have permission to access your home directory, much less, write to it. And you don’t REALLY want your front-facing web server having that permission. I solved this by installing mod_proxy and mod_proxy_http, then running a tiny little lighttpd instance as the user(s) who wanted access to their home directories, with it’s own mod_webdav inserted. I had Apache pass connections to the webdav-server.domain/username vhost/folder alias through the proxy to the lighttpd server for that user, which was exporting that user’s home directory as a single share. I also used lighttpd’s own mod_auth to do the LDAP authentication for each user instead of Apache, because I didn’t want to chance someone getting around the front-facing proxy and connecting directly to one of the DAV servers, obviously. It is worth noting that you will want to set the document-root to either /home or /home/username for the lighty instance, as COPY and MOVE will not work if you don’t. Since authentication is required for the site anyway, it’s not a huge concern to set it to the user’s home directory. This setup seems to work pretty well.

Which brings us to the second problem. Windows XP SP2, Windows Vista and Windows 7 refuse to authenticate to a WebDAV server using BASIC as it’s authentication method (instead of DIGEST). If I were using password files, this would be no problem to change to DIGEST authentication using lighty’s mod_auth, but because I’m using LDAP as a backend, it’s not feasible. You can’t use DIGEST with LDAP because, with DIGEST, the password is never actually sent – just a hash to be compared, but the comparison hash will simply never match what’s in the LDAP database, so there’s no way to do the auth. Instead, the solution seems to be dealing with Windows. After lots and lots of Googling, I found a few registry hacks:


If you set UseBasicAuth (a DWORD value) to 0×00000001 and BasicAuthLevel (also a DWORD value) to 0×00000002, you will be able to authenticate to the DAV server with BASIC authentication. The first parameter enabled BASIC authentication altogether. The latter has three possible values: 0, which disabled BASIC authentication, 1, which enabled it for SSL-secured sites and 2, the value I’m using above, to enable it for all sites. When you’re done, restart the webclient service. (In Vista/7, you can only do this from services.msc or by using an ELEVATED privileges command prompt – not a normal one.)

Now, I wanted to use this with Active Directory to set a user’s N-drive to their home directory. I know it would be easier to use Samba, but if a user is behind a vaguely-filtered internet connection, say Comcast, Virgin Media or Verizon Fios, you can’t actually hit a Samba share over the internet. And VPNs can be even more of a … pain than WebDAV. In theory, you can address a WebDAV share with a UNC path and it works (\\webdav-server.fqdn\sharename). It doesn’t, however, seem to work when you set that UNC path to a drive letter in someone’s AD account. Which sucks.

So I thought, ooh – I know! Map it with a logon script! DERP. Logon scripts have to run from a Samba share. Which we can’t get to. Hence using WebDAV. The solution, if I say so myself, is pretty creative, if not a little over-the-top. I created a folder, say, C:\logon. In there, there is ‘wget.exe’, a Windows binary-only version of the ‘wget’ tool for Linux, which is a simple command-line based HTTP/FTP downloader. Then there’s a file called loader. bat. This file fetches a logon.bat from a web server, and runs it. Then, in their Active Directory profile, I set their logon script to \\localhost\c$\logon\loader.bat – causing them, each time they logon, to download the latest logon.bat and run it. In logon.bat, I use wget again to download a little .exe file written in AutoIT that checks if the registry has already been updated to connect to WebDAV shares using BASIC authentication as above, and if not, requests administrative privileges to update the registry and then restart the webclient service for you. After all this happens, it finally uses ‘net use’ to map the WebDAV shares (the lighty one from earlier as well as an additional SharePoint share) to their relevant drive letters. Scripts below.

The wget.exe I’m using comes from:


@echo off
cd C:\logon
del c:\logon\logon.bat
C:\logon\wget http://some-http-server.fqdn/logon/logon.bat


@echo off
cd C:\logon
del C:\logon\dav-setup.exe
C:\logon\wget http://some-http-server.fqdn/logon/dav-setup.exe

net use N: "http://apache-proxy-to-lighty.fqdn/sharename" /user:username /persistent:yes /savecred
net use T: "http://sharepoint.fqdn/sharename" /user:domain\username /persistent:yes /savecred



$useBasicAuth = RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "UseBasicAuth")
$basicAuthLevel = RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "BasicAuthLevel")

$restartService = 0

if $useBasicAuth <> 1 Then
    RegWrite("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "UseBasicAuth", "REG_DWORD", "0x00000001")
    $restartService = 1

if $basicAuthLevel <> 2 Then
    RegWrite("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "BasicAuthLevel", "REG_DWORD", "0x00000002")
    $restartService = 1

if $restartService = 1 Then
    RunWait(@WindowsDir & "\System32\net stop webclient")
    RunWait(@WindowsDir & "\System32\net start webclient")

tinyurlfs, file2tinyurl & tinyurl2file

IRC Log (or, initial idea conception)

<Ignite> :)
<cmantito> ok, this is gonna sound crazy
<cmantito> really, really crazy
<cmantito> as you might have (or not) seen when it was in the /t, I wrote a set of scripts that could encode files
as URLs to be stored in a tinyurl, and then extracted again
<LeaChim> yes, that was quite cool
<cmantito> now, I'm writing a mountable filesystem, when you copy a file into it, it uploads it to tinyurl, grabs
the tinyurlid, and passes it to a PHP API running on my server and it stores the filename and the tinyurl ID
<LeaChim> why a php script?
<LeaChim> why can't it store this locally?
<cmantito> I'm getting there ;)
<cmantito> and when you read a file, it queries the API running on my server with the filename, gets the urlID, and
allows you to open the file in place
<cmantito> ideally, it's a write-once filesystem, you can put files into it, but not remove them, overwrite them, or
modify them.
<cmantito> the reason for using a remote API for it, is so that it's a unified filesystem
<cmantito> ie, I copy a file into it, and you can copy it right back out again
<cmantito> it's a giant filestore!
<LeaChim> heh
<cmantito> for *everyone*
<LeaChim> quite cool
<cmantito> and
<cmantito> if someone wants to bitch at me for "storing" files of naughty materials
<cmantito> I can use the bittorrent defense
<cmantito> "I only store metadata"
<Ignite> rofl
<LeaChim> heh
<LeaChim> well, you'll be implemented this as fuse right?
<cmantito> yes.
<cmantito> it's half-done
<Ignite> cmantito, so like
<Ignite> cmantito, we can all use it?
<cmantito> yes :D
<Ignite> So we can sort of share warez and shits?
<Ignite> :x
<cmantito> if the links are already in the database, it can read them and list them
<LeaChim> cmantito, for rmdir unlink rename chmod chown truncate you should just return permission denied
<cmantito> LeaChim: exactly! :D
<cmantito> fserr(13)
<cmantito> ENOACCES
<LeaChim> so all you've got left are mkdir symlink link utime, and file creation
<cmantito> well those 4 are unnecessary
<LeaChim> in fact, you might as well not have symlink or link
<LeaChim> no, keep mkdir
<cmantito> exactly
<cmantito> no, it's a flat filesystem
<Ignite> No
<LeaChim> boring
<Ignite> mkdir
<LeaChim> and horrible to list
<LeaChim> add mkdir
* Ignite chants
<cmantito> ffs *fine*
<LeaChim> besides, mkdir is just php side
<Ignite> Rofl
<cmantito> now it's gonne take even longer.
<Ignite> xD
<cmantito> I *was* trying to keep it relatively simple
<cmantito> since if it takes off, tinyurl will kinda notice
<cmantito> Upload a DVD image "What's that, a 4.93921239x10^9 character URL!?"
<cmantito> actaully more
<cmantito> uuencode pads.
<cmantito> butokfine
<LeaChim> cmantito, you might want to add some code to allow you to split up files
<cmantito> I'll put in the ability to add one level of directories.
<cmantito> ONE XD
<LeaChim> lol
<LeaChim> why not more than one?
<LeaChim> it's just going to get horrible to list directories ;
<LeaChim> and maybe you should add something to expire things, so the directory listing doesn't end up being
<cmantito> I'm trying to limit the amount of HTTP queries I have to make for any given requests.
<cmantito> s/(request)s/\1/

Original Scripts

The original file2tinyurl and tinyurl2file scripts were originally published at RackLoad (now defunct) and then at the Xelix wiki, followed by my own CodeWiki to continue development into a FUSE mountable filesystem. They now live here, source available below.

Development Roadmap

It actually works. By using file2tinyurl, and then putting the relevant values into the database manually. the files are accessible. It is not directory-structured yet, and file upload isn’t complete yet either. Please keep adding feature requests, and as soon as a usable version (even without ALL the features) is alpha, I’ll chuck it up here.

Original Proof of Concept Source

These scripts requires the following Perl modules:

  • LWP::UserAgent
  • Convert::UU
  • HTML::Entities
  • URI::Escape

These modules all have dependencies of their own. To easily install the modules and all their dependencies, execute the following command as root and it will prompt you to install the dependencies. Pressing return repeatedly will generally suffice.

cpan install LWP::UserAgent Convert::UU HTML::Entities URI::Escape

This script will upload a file to TinyURL. Yeah, you heard that right.

TinyURL takes a given URL, and sticks it, with a pointer, into it’s database. Why does the URL have to be a URL? It could be any text. Well why not binary data? Just encode the binary data as text, like your mail client does every time you send a binary attachment, and upload that. This script does that for you, too. Then you just need a way to get it out again. See the next post.

What this script does

  1. When given a path to a file name, this will:
  2. Read the file
  3. UUencode the data in the file, converting it to “safe” ASCII.
  4. Strip the newlines from that, since UUencoded data is set at a certain width, and replace them with flags.
  5. Escape and non-URL safe text in the %xx form (ie, ‘~’ becomes %7E, ‘ ‘ becomes %20).
  6. POST the data to TinyURL, and strip the TinyURL and TinyURL ID from the results, displaying it to a console.

As of yet, I have not found a length limit on the original URLs that TinyURL will shorten. Therefore, as of yet, I have not found a filesize limit for “uploaded” files.



### This script was written by cmantito
### This script can be freely modified/distributed/used and is open-source.
### However, I'd appreciate some credit on derivative works.

use LWP::UserAgent;
use Convert::UU qw(uudecode uuencode);
use HTML::Entities;
use URI::Escape;

## If this script runs with strict, you're fucked. So don't add strict.

$filename = shift(@ARGV);

   print "Usage: file2tinyurl /path/to/file\n";

$relFn = $filename;
$relFn =~ s/^(.+)\///ig;

if((-e $filename) && (-r $filename)){
   if(-d $filename){
      fatal("Specified file must not be a directory.");
   fatal("Specified file doesn't exist or isn't readable.");

open(TUFILE, "){
   $rawData .= $_;

$uuData = uuencode($rawData);
$uuData =~ s/\n/_NWLN_/ig;

$urlData = uri_escape($uuData);

$url = "http://".$relFn."/".$urlData;
$formData{'url'} = $url;
$formUrl = "";

$browser = LWP::UserAgent->new;
$response = $browser->post($formUrl, Content => \%formData);

   $tinyData = $response->content;
   ($tinyId) = $tinyData =~ /\http:\/\/tinyurl\.com\/(.+?)\/;
   print "File uploaded successfully.\n";
   print "TinyURL ID (used with tinyurl2file for download): ".$tinyId."\n";
   print  "TinyURL (for reference):".$tinyId."\n";
   print $formUrl;
   fatal("Couldn't contact TinyURL: ".$response->status_line);

sub fatal {
   $errorString = shift(@_);
   print STDERR "Fatal error: ".$errorString."\n";

This script will download a file that was uploaded to TinyURL with file2tinyurl.

TinyURL takes a given URL, and sticks it, with a pointer, into it’s database. Why does the URL have to be a URL? It could be any text. Well why not binary data? Just encode the binary data as text, like your mail client does every time you send a binary attachment, and upload that. Then you just need a way to get it out again. And this is that portion of it. ^_^

What this script does

  1. When given a TinyURL ID (the part of the TinyURL after the,
  2. Retrieves the preview page (
  3. Strips the “original URL” portion out of the page
  4. Extracts the original filename from our “URL”
  5. Looks for newline flags, and puts them back where they belong
  6. Gets rid of any HTML entities, replacing them with the correct ASCII (ie, < should be < and & should be &)
  7. Converts the URL-encoded characters back to ASCII (ie, %20 becomes a space, %7E becomes a ‘~’)
  8. Un-UUencodes the ASCII, getting it back to it’s original form (likely binary data)
  9. Writes that back out to a file, using the original file’s filename which was encoded into it with file2tinyurl.



### This script was written by cmantito
### This script can be freely modified/distributed/used and is open-source.
### However, I'd appreciate some credit on derivative works.

use LWP::UserAgent;
use Convert::UU qw(uudecode uuencode);
use HTML::Entities;
use URI::Escape;

## If this script runs with strict, you're fucked. So don't add strict.

$urlId = shift(@ARGV);

   print "Usage: tinyurl2file [TinyURL ID from file2tinyurl]\n";

$tinyUrl = "".$urlId;

$browser = LWP::UserAgent->new;
$response = $browser->get($tinyUrl);

   $tinyData = $response->content;
   fatal("Couldn't contact TinyURL: ".$response->status_line);

($uuUrl) = $tinyData =~ /\
\<\/b\>\<\/blockquote\>/; $uuUrl =~ s/\
//ig; ($filename, $uuData) = $uuUrl =~ /http:\/\/(.+?)\/(.+)$/; $uuData =~ s/_NWLN_/\n/ig; $uuData = decode_entities($uuData); $rawData = uri_unescape($uuData); $rawData = uudecode($rawData); open(TUFILE, ">".$filename) or fatal($!); print TUFILE $rawData; close(TUFILE); print "File downloaded to ./".$filename."\n"; sub fatal { $errorString = shift(@_); print STDERR "Fatal error: ".$errorString."\n"; exit(5); }