Invoking built-in script from a webpage

To invoke a php script from a webpage, people use something like:

Is it possible to invoke a RouterOS built-in script or API commands from a webpage? (for example on a Routerboard where php is not available, invoking scripts from customized hotspot html pages)? If yes, how?

You mean invoking a script/command from the router’s HTTP server itself? No.

Hotspot pages can be customized with a pseudo language that can check hotspot properties, and output a different HTML depending on their values (including outputting the values themselves), but beyond that, you can’t really “do” anything from the router’s HTTP server.

Thanks for the reply. Damn, that is quite limiting!

The reason I was asking is because I wanted to implement a “forgot password” functionality for my hotspot users. While I see in other threads that this has been done on PC’s that run routerOS and have third party web servers and php capability, I wanted to know whether this was possible on a Routerboard.

Guess I’ll have to keep stalking the developers until they integrate the functionality in the hotspot…

Well, in MikroTik’s defense, a forgotten password feature isn’t hard to implement if you have an external web server.

You just place that server into a “walled garden”, so that it can be accessed without a login, and link to it from the login page. The external server itself can do whatever check ups are needed (e.g. ask for an email, phone or other verifiable personal data) and set a new password, all using the API protocol, which can also connect to RouerBoard devices.

Developing your own features on top of mikrotik is what gives your business value, otherwise you are no different from everyone else. Setting up a simple free radius lamp server takes less than 60 minutes, even less if you use a cloud server.

Get the business logic off the mt and onto your own system and you,ll be amazed at what you can do with the platform.

Nice idea and thanks for putting me on the right track, this is just what I was trying to do. I guess I could host the site on one of the many free web hosting services out there, this way I would be able to offer the functionality to several hotspots on different networks.

If anyone else is interested, there is already some code in this post which does just that.

If I am successful in setting this up on an external web server (zero php experience to date), I will post to describe the process and give something back to the community.

Many, many thanks again. I had realized when reading the above post that it could be done with an external web server and php, but for whatever reason I had in mind that I would have to set up another machine on the local net to achieve this. Somehow your post made me realize that I could host the server on the internet (probably when you mentioned the wall garden).

If you decide to use my client, I have an example code for resetting user passwords in the wiki, created just now. I’m not sure if my scheme will answer your needs… I tried to keep it simple and generic.

I mean, including other stuff like dynamically and temporarily enabling access to the email domain is a little bit more tricky, unless perhaps you have a publicly known “trial” username/password, with which users could login and quickly confirm their identity via an email that would’ve been sent. IMHO, SMS confirmation is the best… but that requires additional hardware.

Awesome, thanks for that. It would have taken me forever to get there (if ever) given that my website programming knowledgeabilty is about nil.

I will try to set this up this up as soon as I find some time, using your code.

Simple and generic does it just fine. I can build on that. My first thought for the lost password was that users would just enter their user name, and the script would have the router either email a new temporary password to their registered address, or email them a link to the form allowing them to change their password. However I guess this would require some sort of security measure to prevent potential attackers to flood the inbox of the users.

  • shipped you some karma :wink:

I understand what you mean, but my purpose here is to keep things as simple and cheap as I can, because I am not doing this professionally. I benevolently administrate (and police!) the internet access at a remote location where we have to share a very limited satellite connection between multiple users. For this purpose, the RouterBoard 450G has been a terrific and cost effective tool, so much so that other similar locations have asked me to implement the same system at their site. I work at these locations, but my job is completely unrelated to this. I just do it on the side, time allowing.

You’re welcome.

That’s not the problem with having an email leading to a password changer.

The problem is your users need to login in order to have internet. And they need internet to access their email => Without login, how could they access their email?

There are only three ways to go about accessing the internet in that situation:

  1. From another network. Easy, but not every client has access to a computer on another network.
  2. With another (public/temporary) known username/password combo. Easy, but not every provider wishes to provide a free account, because there are people who would rather use that than pay, even when the payed account offers significantly greather speeds and unlimited availability.
  3. If you temporarily enable the site their email is on in the walled garden, they could access it without login. This is very tricky, because some email providers let users check their emails from another domain (e.g. GMail is accessed by “accounts.google.com”, not “gmail.com”). You’d have to force users to use one of a set of email providers that you know the domain of. When you have a few clients, all of which you know personally, that’s fine, but for bigger networks, going on this route is unthinkable.

That’s what we thought at my work (a computer repair shop) when we first started… now, this is our 2nd greatest revenue stream, without which we’ll have a very hard time. You said it yourself… you already have other clients who want the same. So - welcome to the club :sunglasses: .

The problem is your users need to login in order to have internet. And they need internet to access their email => Without login, how could they access their email?

I understand that it would not be a viable option in lots of cases, but in my particular case, people who lost their password can simply go to one of their colleagues and borrow their computer/laptop for the sake of checking their email and retrieving the password. So I might build it that way by modifying your code when I get to that point. Or I may decide to do it your way. I still have to think about what will be easier for me and more convenient for them.

Dude, I think I found a few typos in your forgot password script (in red below):

<?php namespace PEAR2\Net\RouterOS; require_once 'PEAR2/Net/RouterOS/Autoload.php'; $errors = array(); //Check if the form was submitted. Don't bother with the checks if not. if (isset($_POST['act'])) { try { //Adjust RouterOS IP, username and password accordingly. $client = new Client('192.168.0.1', 'admin', 'password'); } catch(\Exception $e) { $errors[] = $e->getMessage(); } if (empty($_POST['email'])**)** { $errors[] = 'Email is required.'; } if (empty($_POST['phone'])**)** { $errors[] = 'Phone is required.'; } if (empty($errors)) { //Check if this is an imposter or not $printRequest = new Request('/ip hotspot user print .proplist=.id'); $printRequest->setQuery(Query::where('email', $_POST['email'])->andWhere('comment', $_POST['phone'])); $id = $client->sendSync($printRequest)->getArgument('.id'); if (null === $id) { $errors[] = 'Email or phone does not match that of any user.'; } } if (!isset($_POST['password']) || !isset($_POST['password2'])) { $errors[] = 'Setting a new password is required.'; } if (empty($errors)) { if ($_POST['password'] !== $_POST['password2']) { $errors[] = 'Passwords do not match.'; } else { //Here's the fun part - actually changing the password $setRequest = new Request('/ip hotspot users set'); $client->sendSync($setRequest ->setArgument('password', $_POST['password']) ->setArgument('numbers', $id) ); //Redirect back to the login page, thus indicating success. header('Location: http://192.168.0.1/login.html'); **); should be }** } } ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”>

Forgot your hotspot password? #errors {background-color:darkred;color:white;}

You can reset your hotspot password by filling the following form. You'll be redirected back to the login page once you're done

<?php if(!empty($errors)) { ?>
    <?php foreach ($errors as $error) { ?>
  • <?php echo $error; ?>
  • <?php } ?>
<?php } ?>
  • Email:
  • Phone:
  • Confirm new password:

I am making progress with this, hopefully I will get some results in the next couple of days.

Thanks. I’ve now fixed those.

There were bound to be some errors, since I haven’t tested any of the code on the page :stuck_out_tongue: .

The other three are running without syntax error. It’s hard to figure what’s going on with the web host I am using because they do not offer a SSH interface, so all I was getting in guise of error messages was a blank page when trying to load the form… guess I’d be better off testing on a development machine, but I don’t have one…

I have not tested the scripts functionality or even comms with the routerboard yet. Today was dedicated to figure out a free web host and how and where to get your code online (again, this is the very first time I do this).

Bed time for me now, I will continue tomorrow time allowing.

Make sure your host has PHP 5.3 or greather. To do that, execute the following PHP file:

<?php phpinfo(); ?>

and check the output.

As for how to get it… the download link is right at the page from my signature (the same from the top of the wiki page), at the very top right (the two icons; pick whichever one you can extract; there’s no difference in the contents). You can just download it, and extract the “src” folder somewhere on your server.

Or wait… are you saying you already covered that?

Yes I already did that and the host has php 5.3.

Just now for testing purposes everything is in the public folder (the PEAR2 folder and the .php files with your password scripts). I will move PEAR2 later higher in the structure and put a command in your scripts to add the location in the include path (no access to php.ini on this host).

As I said I have not yet tested the functionality. For example, I see that on this host, one of the disabled php functions is realpath, which is used in your autoload.php. But I think I can find a way around that quite easily.

I think you can safely replace

$file = realpath($path); 

with

$file = $path; 

I placed the realpath() there as a safety precaution if the function is called manually with inputs that may go at a wrong place. For actual _auto_loading, there shouldn’t be any difference.

I am officially stuck. The code never returns from the Client instance creation:

<?php
namespace PEAR2\Net\RouterOS;
require_once 'PEAR2/Net/RouterOS/Autoload.php';
echo 'OK1';
$client = new Client('192.168.0.1', 'admin');
echo 'OK2';
?>

When I load the page I see the OK1 just fine but not OK2, meaning that the code crashes during the new Client directive. I also tried not using Autoload and loading all the classes in RouterOS and Transmitter manually, still no luck.

My test setup is as follows: my base folder with the host is /home/www/ and I created a subdomain so the public files go into /home/www/mysubdomain.mywebhost.net

For testing, I put your PEAR2 directory into this public folder, and the .php script above at the same level as the PEAR2 directory.

I have also verified that the setup is sound by creating a very simple class /home/www/mysubdomain.mywebhost.net/PEAR2/Net/RouterOS/classTime.php:

<?php
namespace PEAR2\Net\RouterOS;
    class Time {  
      function GenerateCurrentTime(){  
        $sTime = gmdate("d-m-Y H:i:s");  
        return $sTime;  
      }  
    }  
?>

and a php file using that class to display the time: /home/www/mysubdomain.mywebhost.net/time.php:

<?php  
namespace PEAR2\Net\RouterOS;
require_once 'PEAR2/Net/RouterOS/classTime.php';  
    $oTime = new Time;  
    $sTime = $oTime->GenerateCurrentTime();  
    print 'The time is: ' . $sTime; 
?>

and this works as expected.

Any clue hint how to debug this?

Create a new file with the following in it:

<?php
namespace PEAR2\Net\RouterOS;

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'On');

require_once 'PEAR2/Net/RouterOS/Autoload.php';
echo 'Autoload.php is loaded...';

try {
    $client = new Client('192.168.0.1', 'admin');
    echo 'Client is created...';
} catch(\Exception $e) {
    echo 'Client not created because of the following exception: ', $e;
} 

If Client fails to estabilish a valid connection for whatever reason (wrong username or password, incorrect IP or port, router unavailable, API not enabled in the router, no API priviliges for that user…), an exception is thrown. In turn, if you don’t have a catch, PHP will fail with a fatal error (which is not displayed on screen with your host’s current settings).

In case the problem is something in PHP itself (like, if your host has disabled a required function, such as stream_socket_client()), there will be a fatal error without any exceptions being thrown. The ini_set() and error_reporting() at the top ensure we’ll be able to see such errors (unless your host has also disabled those functions, in which case you should write them an angry letter).


BTW, while we’re on it… didn’t you say you’re getting a free web hosting (as in “a web server outside your network”)? If that’s the case, the IP at Client should be your router’s public IP, not its private one. The username and password also need to match (you don’t have “admin” standing out there without a password, do you?).

Thanks for your help with the debug code. Here is the error message (I obfuscated my personal details):

Client not created because of the following exception: exception 'PEAR2\Net\Transmitter\SocketException' with message 'Failed to initialize socket.' in /srv/disk8/xxxxxxx/www/mysubdomain.myhost.net/PEAR2/Net/Transmitter/TcpClient.php:109 Stack trace: #0 /srv/disk8/xxxxxxx/www/mysubdomain.myhost.net/PEAR2/Net/Transmitter/TcpClient.php(93): PEAR2\Net\Transmitter\TcpClient->createException('Failed to initi...', 7) #1 /srv/disk8/xxxxxxx/www/mysubdomain.myhost.net/PEAR2/Net/RouterOS/Communicator.php(110): PEAR2\Net\Transmitter\TcpClient->__construct('myexternalIP', 8728, false, NULL, 'api_user', NULL) #2 /srv/disk8/xxxxxxx/www/mysubdomain.myhost.net/PEAR2/Net/RouterOS/Client.php(105): PEAR2\Net\RouterOS\Communicator->__construct('myexternalIP', 8728, false, NULL, 'api_user', NULL) #3 /srv/disk8/xxxxxxx/www/mysubdomain.myhost.net/test11.php(11): PEAR2\Net\RouterOS\Client->__construct('myexternalIP', 'api_user', 'api_user_password') #4 {main} Socket error number:111 Socket error message:Connection refused

Alos here is the list of functions that are supposedly disabled for free users on my host:

The following PHP functions are disabled on free accounts due to system/security reasons: allow_url_fopen, fsockopen, pfsockpen, getrusage, get_current_user, set_time_limit, getmyuid, getmypid, dl, leak, listen, chown, chgrp, realpath, link, exec, passthru, curl_init.

I verified that the router is accessible and the API service is enabled and listening on port 8728 and I opened the port in the firewall. Do you think this happens because of limitations from my hosting site? Should I try another one?

To answer the question in your previous post: yes I do know that I have to use the external IP of the router, and I created a user with limited privileges and a strong password for the purpose. I just used the example of your code to avoid posting my IP and user names all over the forum.