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
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.
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.
- 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.
- 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 '^]'.