Setting up external hotspot with Ubiquiti (ubnt) and debugging

Robert AndresenProgramming, Tutorials 2 Comments

My setup:

  • Webserver: Ubuntu 16.04 with default Apache2, PHP7 and MySQL.
    The webserver also has a local issued certificate with HTTPS.
  • UniFi Controller: 4.8.18 (Running on a Windows 10 client)
  • Laptop with wireless, running Windows 10, for testing.

This article will focus more on debugging than on the setup itself, as there are multiple of howto-guides everywhere on the Internet.

My guest control settings

unifi_guest_control

 

My first step was to download the «Simple PHP External Portal.zip» from this thread https://community.ubnt.com/t5/UniFi-Wireless/Simple-External-Portal-in-PHP/m-p/427035#M35153.

As the thread was pretty much up to date, i thought the portal and files (first posted in 2012) also has been updated along the way – but NO! Ubnt has changed the payload from POST to JSON. As the Simple-External-Portal linked to above had JSON payload for the API call, I thought that problem was taken care of – but after 5-6 hours of googling, I finally found out it wasn’t. The login also need to be sent as a JSON-payload.

The out-of-the-box issue, was that I just was redirected back to the portal webpage after trying to connect. Indicating a curl/auth issue. You can also log in to the controller and click on clients, to see if the client has been authorized or not.

unifi_clients

 

My working code was copied from: https://github.com/kaptk2/portal. This code has JSON payload for the login.

I still needed to do some minor changes to the code.

  1. The $unifi parameters was not sent to the function. So I removed $unifi as a parameter in the function, and added global $unifi in the function instead.
  2. For some reason none of my webservers looks like there available to create the cookie file, so I altered the path and created the cookie-file manually. Probably some permission issues, but didn’t troubleshoot that part any more.

My final sendAuthorization function looks like this:

<?php
function sendAuthorization($id, $minutes) {
	global $unifi;

	// Start Curl for login
	$ch = curl_init();
	// We are posting data
	curl_setopt($ch, CURLOPT_POST, TRUE);


	// Set up cookies
	//$cookie_file = "/tmp/unifi_cookie";
	$cookie_file = "/var/www/krokenweb.local/guest/unifi_cookie.txt";
	curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
	curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);


	// Allow Self Signed Certs
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
	curl_setopt($ch, CURLOPT_SSLVERSION, 1);
	// Login to the UniFi controller
	curl_setopt($ch, CURLOPT_URL, $unifi['unifiServer']."/api/login");
	$data = json_encode(array("username" => $unifi['unifiUser'],"password" => $unifi['unifiPass']));
	curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
	$login = curl_exec($ch);

	// Send user to authorize and the time allowed
	$data = json_encode(array(
		'cmd'=>'authorize-guest',
		'mac'=>$id,
		'minutes'=>$minutes));
	
	// Make the API Call
	curl_setopt($ch, CURLOPT_URL, $unifi['unifiServer'].'/api/s/default/cmd/stamgr');
	curl_setopt($ch, CURLOPT_POSTFIELDS, 'json='.$data);
	curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
	$api = curl_exec ($ch);

	// Logout of the connection
	curl_setopt($ch, CURLOPT_URL, $unifi['unifiServer']."/logout");
	curl_exec ($ch);
	$logout = curl_close ($ch);

	sleep(6); // Small sleep to allow controller time to authorize
}
?>

 

Some troubleshooting and debugging along the way:

Most of the troubleshooting was from testing the Simple-External-Portal, but added them as they could be useful if something doesn’t work as expected.

SSL Version

The «curl_setopt($ch, CURLOPT_SSLVERSION, 3);» didn’t give me any output from Curl at all. The apache error-log was also blank.

So I changed to «curl_setopt($ch, CURLOPT_SSLVERSION, 1);», and outputted the requests in the page. I finally got some feedback from the code.

...
$login = curl_exec($ch);
echo "<pre>";
	print_r($login);
echo "</pre>";
...
$api = curl_exec ($ch);
echo "<pre>";
	print_r($login);
echo "</pre>";

This gave me some feedback like «api.err.LoginRequired». So the curl is working, but not the authorization.

ob_start()

I also removed ob_start() from Simple-External-Portal, as this can prevent output. Looks like this isn’t in the github-code. You can also just try to move it on the top of your code, under session_start().

Force some error messages

I also added:

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_VERBOSE, 1);

…in the curl.

This gave me some output in the apache error-log, like this:

< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Cache-Control: private
< Expires: Thu, 01 Jan 1970 00:00:00 UTC
< Location: https://192.168.1.191:8443/login?redirect=%2Fmanage
< Content-Length: 0
< Date: Fri, 20 May 2016 02:09:27 GMT
<
* Connection #0 to host 192.168.1.191 left intact
* Issue another request to this URL: 'https://192.168.1.191:8443/login?redirect=%2Fmanage'
* Found bundle for host 192.168.1.191: 0x5644312560e0 [can pipeline]
* Re-using existing connection! (#0) with host 192.168.1.191
* Connected to 192.168.1.191 (192.168.1.191) port 8443 (#0)
> GET /login?redirect=%2Fmanage HTTP/1.1
Host: 192.168.1.191:8443
Accept: */*

< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Accept-Ranges: bytes
< ETag: W/"5796-1462440926000"
< Last-Modified: Thu, 05 May 2016 09:35:26 GMT
< Content-Type: text/html
< Content-Length: 5796
< Vary: Accept-Encoding
< Date: Fri, 20 May 2016 02:09:27 GMT

From this, it looked like it was trying to redirect me to /manage, which is the portal. We don’t actually want to go there, but this tells me that the authorization probably is working.

Cookie-issue

As the authorization is working, the «api.err.LoginRequired»-problem was that I didn’t have a auth when the code tried to make the API-call after the login, so it’s probably a cookie issue.

I added this, right after CURLOPT_COOKIEJAR and CURLOPT_COOKIEFILE:

if (! file_exists($cookie_file))
{
	echo "Cookie, $cookie_file, file missing.";
	exit;
}

if (! is_writable($cookie_file))
{
	echo "Cookie, $cookie_file, file is not writable.";
	exit;
}

And I got an error that the cookie didn’t exist. I created it manually, but then it wasn’t writable. So I just changed the path and created the cookie with full permissions (0777) to be sure, «$cookie_file = «/var/www/krokenweb.local/guest/unifi_cookie.txt»;».

Testing connection

You should also test if the webserver is allowed to communicate with the controller on the port. You can do a simple telnet test, telnet <controller_ip> 8443.

root@krokenweb:~# telnet 192.168.1.191 8443
Trying 192.168.1.191...
Connected to 192.168.1.191.
Escape character is '^]'.

 

The result

unifi_guest_portal