In the absence of a Terminal Server, having users remotely use the software on their desktop PCs is often easier than having to manage software packages on laptops which may be part of a generic pool. In an energy conscious business there is the problem of what to do if one of your remote users wants to get at their desktop PC while it’s asleep or powered down. Wake on LAN works by sending a magic packet – featuring the target PC’s MAC address – to the network broadcast address. A MAC address is pretty unwieldy, so the ideal solution is an intranet page allowing users to wake a PC by hostname once they’re connected to the VPN.
To do this you will need some kind of host database to link hostnames to MAC addresses (remember – the machines could be switched off, so the DHCP database is no good). Your intranet server must also be on the same subnet as the machines you intend to wake. I have only used this in a single subnet so I haven’t investigated scalability, but it just looks like a case of enabling directed broadcasts on your routers. My login script updates host database entries and collects other WMI info such as make & model, tag number, spec, etc.
I implemented this Wake on LAN four years ago so there may be neater ways of doing it by now. At the time I couldn’t do the whole thing in ASP because there was no free socket library for VBScript, so I used Perl to create the magic packet. I used a generic wakeonlan.pl script by José Pedro Oliveira and tweaked it to post back to the ASP page.
Here are the required scripts – the first is ASP part you would need for the Intranet page:
<% Language=VBScript %>
<p>Wake your PC to allow you to connect to it remotely.</p>
<form method="get" action="./default.asp">Name of PC to power on: <input name="hostname" maxlength=14><input type="submit" value="wake"><br>
<%
Dim strHostname
strHostname = Request.QueryString("hostname")
'check query string from form above
If Not strHostname = "" Then
'remove potential SQL injection attack characters
strHostname = killChars(strHostname)
Dim strConnection, objConnection, objRecordSet, objCommand, objResult
'create connection object
strConnection = "Provider=SQLOLEDB; Data Source=sqlsvr.domain.com; Initial Catalog=HostDB;User Id=HostDB_RO;Password=yourpassword"
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open strConnection
'create command object
set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection
'check to see if a MAC exists for this hostname - you'll need to customize this depending on your database
objCommand.CommandText = "SELECT * FROM Inventory WHERE Hostname='" & strHostname & "'"
set objRecordSet = objCommand.Execute
If Not objRecordSet.EOF Then
'if it does exist then wake it
Response.Redirect ("./wakeonlan.plx?MAC=" & objRecordSet.Fields.Item("MAC").value)
Else
Response.Write ("<em>Unknown computer: '" & strHostname & "'.</em>")
End If
objRecordSet.Close
Set objCommand = nothing
objConnection.Close
Set objConnection = nothing
End If
'check query string for result from wakeonlan.plx
Dim strResult, strMAC
strResult = Request.QueryString("result")
If strResult = "True" Then
Response.Write("<em>Wake-up packet sent. Wait around one minute before connecting.</em>")
End If
If strResult = "False" Then
Response.Write("<em>Invalid MAC.</em>")
End If
'sanitize against SQL injection attacks
Function killChars(strWords)
Dim arrBadChars, strNewChars
arrBadChars = array("select", "drop", ";", "--", "insert", "delete", "xp_", "'", "=", " ")
strNewChars = strWords
For i = 0 To uBound(arrBadChars)
strNewChars = replace(strNewChars, arrBadChars(i), "")
Next
killChars = strNewChars
End Function
%>
</form>
And this is wakeonlan.plx:
#!/usr/bin/perl
#
# wakeonlan.plx
# based on José Pedro Oliveira's wakeonlan.pl v1.4.2.3 <jpo@di.uminho.pt>
use strict;
use Env "QUERY_STRING";
use Socket;
# your LAN broadcast address
my $DEFAULT_IP = '172.16.1.255';
my $DEFAULT_PORT = getservbyname('discard', 'udp');
my %FORM;
my $result;
&parse_query_string;
&wake($FORM{MAC});
print 'Status: 302 Moved', "\r\n", 'Location: ./default.asp?result=', $result, "\r\n\r\n";
sub parse_query_string {
my ($buffer, @pairs, $pair, $name, $value);
if (length ($ENV{'QUERY_STRING'}) > 0){
$buffer = $ENV{'QUERY_STRING'};
@pairs = split(/&/, $buffer);
foreach $pair (@pairs){
($name, $value) = split(/=/, $pair);
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$FORM{$name} = $value;
}
}
}
sub wake {
my $hwaddr = shift;
my $ipaddr = $DEFAULT_IP;
my $port = $DEFAULT_PORT;
my ($raddr, $them, $proto);
my ($hwaddr_re, $pkt);
# Validate hardware address (ethernet address)
$hwaddr_re = join(':', ('[0-9A-Fa-f]{1,2}') x 6);
if ($hwaddr !~ m/^$hwaddr_re$/) {
$result = "False";
return undef;
}
# Generate magic packet
foreach (split /:/, $hwaddr) {
$pkt .= chr(hex($_));
}
$pkt = chr(0xFF) x 6 . $pkt x 16;
# Allocate socket and send packet
$raddr = gethostbyname($ipaddr);
$them = pack_sockaddr_in($port, $raddr);
$proto = getprotobyname('udp');
socket(S, AF_INET, SOCK_DGRAM, $proto) or die "socket : $!";
setsockopt(S, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt : $!";
$result = "True";
send(S, $pkt, 0, $them) or die "send : $!";
close S;
}