Wake on LAN for VPN users

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s