Wednesday, April 17, 2013

Automated guest Wifi password (Linksys Smart Wifi)

Here's my requirement: I want to automatically change the password on my guest wireless network, so that if I hand it out to guests, they can't pass it on to others or use it indefinitely.  That's all :)

I have 3 Wifi routers around the house.  Obviously I don't want to log in to all three each day and change the passwords.  With the risk of making mistakes.  So I want my linux server to take care of this feat.

I had implemented this once already with these three devices, but I recently upgraded their firmware and the mechanism broke completely.  The Wifi routers are Linksys 4200's which I upgraded to firmware 2.1.39.something.  This is called Linksys Smart Wifi firmware.  To make things even worse, I have them configured in bridge mode, so most functionality is not available.  There isn't even a UI to change guest network parameters manually even if I wanted to!
I knew guest network is still available because the SSID is broadcast, and I can see it on my laptop.

So I logged on to the router, and used the magic touch: F12 key opens developer tools in Google Chrome (I think it does now in IE as well).

I noticed several HTTP POST messages to the device like this:

Request URL: http://192.168.2.13/JNAP/
Request Method: POST

Headers:

Accept:*/* Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3 
Accept-Encoding:gzip,deflate,sdch 
Accept-Language:en-US,en;q=0.8,nl;q=0.6 
Authorization:Basic YWRtayouwishTAwN3M= 
Connection:keep-alive 
Content-Length:1312
Content-Type:application/json; charset=UTF-8 
Host:192.168.2.13 
Origin:http://192.168.2.13 
Referer:http://192.168.2.13/ User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31 
X-JNAP-Action:http://cisco.com/jnap/core/Transaction 
X-JNAP-Authorization:Basic YWRtayouwishTAwN3M=
X-Requested-With:XMLHttpRequest 

  Payload:

[{"action":"http://cisco.com/jnap/wirelessap/GetRadioInfo","request":{}},{"action":"http://cisco.com/jnap/locale/GetTimeSettings","request":{}},{"action":"http://cisco.com/jnap/routerleds/GetRouterLEDSettings","request":{}},{"action":"http://cisco.com/jnap/firmwareupdate/GetFirmwareUpdateSettings","request":{}},{"action":"http://cisco.com/jnap/core/GetAdminPasswordRestrictions","request":{}},{"action":"http://cisco.com/jnap/core/GetDeviceInfo","request":{}},{"action":"http://cisco.com/jnap/guestnetwork/GetGuestNetworkSettings","request":{}},{"action":"http://cisco.com/jnap/router/GetWANSettings","request":{}},{"action":"http://cisco.com/jnap/router/GetMACAddressCloneSettings","request":{}},{"action":"http://cisco.com/jnap/router/GetIPv6Settings","request":{}},{"action":"http://cisco.com/jnap/router/GetLANSettings","request":{}},{"action":"http://cisco.com/jnap/networkconnections/GetNetworkConnections","request":{}},{"action":"http://cisco.com/jnap/router/GetDHCPClientLeases","request":{}},{"action":"http://cisco.com/jnap/router/GetRoutingSettings","request":{}},{"action":"http://cisco.com/jnap/routermanagement/GetManagementSettings","request":{}},{"action":"http://cisco.com/jnap/routerupnp/GetUPnPSettings","request":{}},{"action":"http://cisco.com/jnap/firewall/GetALGSettings","request":{}}]

Interesting no? I left out the response, but there was a truck load of useful information.  The response was organized per action.  So for each 'action' in the payload, a response section is returned in JSON.
The routers apparently use some kind of REST-like protocol called JNAP.  Never heard of it.  But there's on of interest:

{"action":"http://cisco.com/jnap/guestnetwork/GetGuestNetworkSettings","request":{}}

So I used curl on a linux box to send this separate request:

USER="admin:router_password"
POST_DATA="[{\"action\":\"http://cisco.com/jnap/guestnetwork/GetGuestNetworkSettings\",\"request\":{}}]"
curl -H "X-JNAP-Action:http://cisco.com/jnap/core/Transaction" -H "X-JNAP-Authorization:Basic YWRtayouwishTAwN3M=" -d $POST_DATA -u $USER http://192.168.2.13/JNAP/

And this returns:

{
  "result": "OK",
  "responses": [
  {
  "result": "OK",
  "output": {
    "isGuestNetworkEnabled": true,
    "broadcastGuestSSID": true,
    "guestSSID": "ducnet-guest",
    "guestPassword": "guestpwd",
    "maxSimultaneousGuests": 5,
    "canEnableGuestNetwork": true,
    "guestPasswordRestrictions": {
      "minLength": 4,
      "maxLength": 32,
      "allowedCharacters": [
        {
        "lowCodepoint": 48,
        "highCodepoint": 57
        },
        {
        "lowCodepoint": 65,
        "highCodepoint": 90
        },
        {
        "lowCodepoint": 97,
        "highCodepoint": 122
        }
        ]
      },
    "maxSimultaneousGuestsLimit": 50
    }
  }
  ]
}


So with some trial and error, I figured out that I can send following payload as a POST request to the router and the password would be set:

[{"action":"http://cisco.com/jnap/guestnetwork/SetGuestNetworkSettings","request":{"isGuestNetworkEnabled":true,"broadcastGuestSSID":true,"guestSSID":"ducnet-guest","guestPassword":"otherpwd","maxSimultaneousGuests":5}}]
Pay attention to these things when sending the request:

  • Remove spaces from the JSON in the payload.  The response appears as if it worked, but it doesn't.
  • Set the 2 JNAP headers (X-JNAP-Action and X-JNAP-Authorization)
  • Provide the username and password for the HTTP authentication (-u param with curl)

The response looks like this:

{
  "result": "OK",
  "responses": [{
    "result": "OK"
  }]
}

And that's it.  Poor all this in some additional scripts and add cron to launch it whenever you like (e.g. use current month and year as password).  Works for me!