Community discussions

MikroTik App
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Invoking built-in script from a webpage

Mon Jun 04, 2012 8:32 am

To invoke a php script from a webpage, people use something like:
<form name="form1" method="post" action="myscript.php">

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?
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 1:16 pm

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.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 5:08 pm

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...
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 5:17 pm

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.
 
User avatar
TheWiFiGuy
Member
Member
Posts: 351
Joined: Thu Nov 24, 2011 7:26 pm
Location: UK

Invoking built-in script from a webpage

Mon Jun 04, 2012 6:12 pm

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.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 7:58 pm

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.
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).
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 9:00 pm

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.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 9:23 pm

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:
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 9:48 pm

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.
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.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Mon Jun 04, 2012 10:17 pm

Awesome, thanks for that.
You're welcome.
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.
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.
my job is completely unrelated to this. I just do it on the side, time allowing.
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 8) .
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 6:16 am

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.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 10:11 pm

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">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Forgot your hotspot password?</title>
<style type="text/css">#errors {background-color:darkred;color:white;}</style>
</head>
<body>
<div>
<h1>You can reset your hotspot password by filling the following form.
You'll be redirected back to the login page once you're done</h1>
<?php if(!empty($errors)) { ?>
<div id="errors"><ul>
<?php foreach ($errors as $error) { ?>
<li><?php echo $error; ?></li>
<?php } ?>
</ul></div>
<?php } ?>
<form action="" method="post">
<ul>
<li>
<label for="email">Email:</label>
<input type="text" id="email" name="email" value="" />
</li>
<li>
<label for="phone">Phone:</label>
<input type="text" id="phone" name="phone" value="" />
</li>
<li>
<label for="password2">Confirm new password:</label>
<input type="password" id="password2" name="password2" value="" />
</li>
<li>
<input type="submit" id="act" name="act" value="Reset password" />
</li>
</ul>
</form>
</div>
</body>
</html>

I am making progress with this, hopefully I will get some results in the next couple of days.
Last edited by daviddem on Tue Jun 05, 2012 10:16 pm, edited 1 time in total.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 10:16 pm

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 :P .
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 10:33 pm

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.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 10:38 pm

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?
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 10:53 pm

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.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Tue Jun 05, 2012 11:07 pm

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 autoloading, there shouldn't be any difference.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 4:09 pm

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?
Last edited by daviddem on Wed Jun 13, 2012 4:31 pm, edited 1 time in total.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 4:43 pm

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?).
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 7:54 pm

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.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 8:02 pm

The last part of the stack trace shows the problem:
Socket error message:Connection refused
So yes, this is most probably a problem with the host. I don't see stream_socket_client() in the list of disabled functions, but they might've disabled network connections on the level of their firewall, thus causing this message.

The only other possibility is that your router refuses the connection due to some setting you haven't noticed (you said you've enabled it in the firewall; maybe you haven't set it properly). Either way, the solution is to try connecting from another host, where you'd know they won't stop you. Ideally from a computer you control that is out of the network.
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.
OK. Sorry for doubting that. I had to check, as I've now had two instances of people either forgetting to enable the API or forgetting to give sufficient permissions to the user.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 9:50 pm

Success!

I tried two other hosts who both were giving me a connection timeout error. Then on a hunch I moved the API service on the router to port 21, and modified the script accordingly:
<?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('myexternalIP', 'api_user', 'api_user_password', 21);
    echo 'Client is created...';
} catch(\Exception $e) {
    echo 'Client not created because of the following exception: ', $e;
} 
$responses = $client->sendSync(new Request('/ip/arp/print'));
 
foreach($responses as $response) {
    if ($response->getType() === Response::TYPE_DATA) {
        echo 'IP: ', $response->getArgument('address'),
            ' MAC: ', $response->getArgument('mac-address'),
            "\n";
    }
}
?>
and satisfyingly, the content of my arp cache gets dumped on the page! So it definitely seems to be the hosts reverse firewalling most ports.

I will leave it at that for tonight. Tomorrow I will look for a working port other than 21.

I am a bit concerned about this from a security standpoint. The API service does not seem to be encrypted at all (?) so does that mean that all the comms between the web server and the router are in clear text, including the api_user password?
Last edited by daviddem on Wed Jun 06, 2012 10:17 pm, edited 1 time in total.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Wed Jun 06, 2012 10:00 pm

Everything but the password is in clear text. It's a CHAP challenge, just like in Hotspot - before login you get a random string, you encrypt your password with MD5, using the provided random string as a salt, and send the result (with all this being done automatically by the client, of course).

MD5 is nowadays easy to be "decrypted" to a string that would produce the same hash, but making it be decrypted to a specific string that would match a specified pattern (the pattern in this case being a null byte, followed by the password, followed by the random string) is fortunatly still relatively hard.

If you have IPv6 at the router, you can have encryption on a that level. Furthermore, you could only whitelist your host's IP at the service tab, to prevent arbitrary devices from trying their luck (i.e. even if they snoop and find out the password, it won't matter).

If connections time out on one port, but not on another, this can only mean one of two things - you have a third setting in the router that gives weight to port 21 or (the more likely possibility), the host is, at their firewall, silently discarding outgoing connections on all ports that clients haven't asked them about. FTP's 21, being a popular protocol, is obviously a heavily requested port that's already allowed. Hopefully, if you ask them to enable 8728, they will.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Thu Jun 07, 2012 3:10 pm

Hello, I am afraid I need your help again with your API. My cunning plan for the "forgot password" functionality is that users will fill their username and email address, and if these match then they will be sent an email with their password (they can borrow a colleague's computer for a minute to check their email).

The first part where the username and email are checked works fine. My plan for the second part is that the php script will instruct the router to run a script (local to the router) that will do the job of looking up the password and sending the email. I already wrote this script and tested it, all is fine on that side. The script needs to take the hotspot user name as an argument in order to retrieve the email address and password. As I understand, this can only be done via global variables in RouterOS. When I manually set the global variable directly on the router and run the script, all is well. Now I am trying to get the php script to instruct the router to set this global variable to the correct hotspot user name. Try as I might, I can't get this to work:
$globalRequest = new Request('/global');
$globalRequest->setArgument('name', 'hsusername');
$globalRequest->setArgument('value', 'ActualHSUserName');
$client->sendSync($globalRequest);
or
$client->sendSync(new Request('/global name="hsuname" value="ActualHSUserName"'));
both obstinately do not seem to set the value of my hsusername global variable to ActualHSUserName.

To run the emailing script, I use:
$mailRequest = new Request('/system script run');
$mailRequest->setArgument('number', 'mailing_script');
$client->sendSync($mailRequest);
and I can see that this works fine because the run count of the script increases, but since the script cannot does not get the value of the global variable, it does not actually send the email.

What am I doing wrong? It seems to me my api user has the necessary privileges (write and sensitive included). Is it that the global variable is set in one request and the script run in another, to which the scope of the global variable does not extend?
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Thu Jun 07, 2012 4:54 pm

Commands that start with semicolon are not available with the API protocol. Plus, variables can't be added in any way.

You can edit existing variable values from "/system script environment", but I remember there was an issue where the new value was only made available after the API session is over. I don't know if the latest versions have fixed this yet.

To avoid this, the best thing you can do is to dynamically create the script with the variables being replaced right at creation, and then run that new script. To avoid collisions in case of multiple users forgetting their passwords, you should name the new script after the username.

In code (assuming $username and $email are defined and are already verified to be correct by the start of this):
function safeForScripting($value) {
    $result = '';
    for ($i=0, $l=strlen($value); $i<$l; ++$i) {
        $result .= '\\' . str_pad(strtoupper(dechex(ord($value[$i]))), 2, '0', STR_PAD_LEFT);
    }
    return $result;
}

$scriptName = 'mailer_' . $username;
$scriptingSafeUsername = safeForScripting($username);
$scriptingSafeEmail = safeForScripting($email);

$addRequest = new Request('/system script add');
$addRequest
    ->setArgument('name', $scriptName)
    ->setArgument('source', ':local p [/ip hotspot user get "' . $scriptingSafeUsername . '" password]
/tool e-mail send subject="Forgotten hotspot password" to="' . $scriptingSafeEmail . '" body="Your hotspot password is: $p"');
$client->sendSync($addRequest);

$runRequest = new Request('/system script run');
$client->sendSync($runRequest->setArgument('number', $scriptName));

$removeRequest = new Request('/system script remove');
$client->sendSync($removeRequest->setArgument('numbers', $scriptName)); 
P.S. From your first code samples, the first one is more "stable". Under the hood, the second form is translated to the first. If a command doesn't work with the first form, it's safe to say it won't work with the second one either.

P.S.S. Your first idea about a link to a personalized password changer, although slightly more complex to implement, would be far better from a security standpoint... or at least about instilling confidence.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Thu Jun 07, 2012 11:11 pm

Thanks for your code, man you're fast, you must be dreaming in php! I had started on your idea when I saw that you added the code to your post, but I was only at the first couple of lines. I would probably have made it OK, except for the clever safeForScripting function to prevent code injection (I also limited my input lengths as an extra precaution).

edit: ignore next paragraph, I found the problem (number->numbers) and everything works now:
$removeRequest = new Request('/system script remove');
$client->sendSync($removeRequest->setArgument('numbers', $scriptName));  
I got your code to run on my host, and everything works as expected except, weirdly, the removal. The script does not get deleted as planned after execution. I tried to grant full permissions to the user, tried to insert a 20 seconds sleep after the run request... no way. How can I see the error message output by the router upon the failed deletion, if any?

Also wanted to ask: how does script policy work on RouterOS? I unset the "sensitive" policy of a script that reads the passwords of hotspot users, but the script was still reading the passwords!? Is it because it is a user who has the "sensitive" policy switched on who runs the script??

Concerning security and confidence, you are probably right, but with the link (which would necessarily have to be to an external website without SSL because I don't want to pay for a certificate), the hotspot passwords themselves would have to travel to and from the web server in clear text, so how is that more secure? (unless I implement chap encryption?) And at the end of the day, I am not really trying to protect sensitive information, these passwords only give internet access to a bunch of guys in a remote location and they're only useful to them. But it's true there is always the off chance that one of them will try sniffing clear text passwords off the air though.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Fri Jun 08, 2012 5:41 pm

Thanks for your code, man you're fast, you must be dreaming in php!
Almost :lol: . I sort of dream in algos, which may or may not be implemented in PHP 8) (I mean silly and unrealistic algos... like "where do packets go when they die because their time to live is up?" or "how do you find if a bite of a kilo of bytes would be delicious?"... no fancy stuff like graphics, crypthography or things like that).
How can I see the error message output by the router upon the failed deletion, if any?
Any error messages by the router are part of the returned result set, just like any other response. You can inspect the different responses by looping over them, like:
foreach ($client->sendSync(new Request('/system script run number="TEST"')) as $response) {
    if ($response->getType() === Response::TYPE_ERROR) {
        echo "Error occured:\n";
        foreach ($response->getAllArguments() as $name => $value) {
            echo $name, ': ', $value, "\n";
        }
        echo "====\n";
    } else {
        echo "A response which is not an error\n=====\n";
    }
} 
Or if you're only interested in a type (like, if you want to see only errors, if there are any), you can filter them in advance, like:
foreach ($client->sendSync(new Request('/system script run number="TEST"'))->getAllOfType(Response::TYPE_ERROR) as $response) {
    foreach ($response->getAllArguments() as $name => $value) {
        echo $name, ': ', $value, "\n";
    }
    echo "====\n";
} 
In theory, you could also just use var_dump() on the returned value from sendSync(), but in practice, the data there is somewhat cryptic for anyone who isn't familiar with the protocol and client (i.e. pretty much anyone but me :lol: ).
Also wanted to ask: how does script policy work on RouterOS? I unset the "sensitive" policy of a script that reads the passwords of hotspot users, but the script was still reading the passwords!? Is it because it is a user who has the "sensitive" policy switched on who runs the script??
Sounds like a bug, and I can confirm it happens on my RouterOS too ( 5.8 ). If you have 5.17 and it still occurs, you may want to contact support.
Concerning security and confidence, you are probably right, but with the link (which would necessarily have to be to an external website without SSL because I don't want to pay for a certificate), the hotspot passwords themselves would have to travel to and from the web server in clear text, so how is that more secure? (unless I implement chap encryption?) And at the end of the day, I am not really trying to protect sensitive information, these passwords only give internet access to a bunch of guys in a remote location and they're only useful to them. But it's true there is always the off chance that one of them will try sniffing clear text passwords off the air though.
Fair enough. I suppose anyone hacker enough to guess a username/email combo is also hacker enough to guess an email/password(for email) combo and/or snoop around long enough to see a user change it.
 
daviddem
Frequent Visitor
Frequent Visitor
Topic Author
Posts: 62
Joined: Sun Sep 18, 2011 12:16 pm

Re: Invoking built-in script from a webpage

Sun Jul 29, 2012 11:30 am

Everything but the password is in clear text. It's a CHAP challenge, just like in Hotspot - before login you get a random string, you encrypt your password with MD5, using the provided random string as a salt, and send the result (with all this being done automatically by the client, of course).

MD5 is nowadays easy to be "decrypted" to a string that would produce the same hash, but making it be decrypted to a specific string that would match a specified pattern (the pattern in this case being a null byte, followed by the password, followed by the random string) is fortunatly still relatively hard.
Hello Sir,

Thanks to your help my forgot password functionality is now working as I want.

I have an unrelated question for you though, if you don't mind. I generally understand how CHAP encryption is done as per rfc 1994, but I don't get why in the Mikrotik hotspot login page we have:
hexMD5('$(chap-id)' + document.login.password.value + '$(chap-challenge)');

What is chap-id? How are chap-id and chap-challenge generated by the routerOS?

Any more details you would have about the login process is welcome.
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Sun Jul 29, 2012 1:53 pm

It looks like it's simply an additional token (a byte really) used to add some entropy. Doesn't look any more secure than the API login, which is basically the same, except the ID is always 0... or maybe I'm overlooking something, I don't know.
 
riemann
just joined
Posts: 5
Joined: Wed Sep 28, 2005 8:52 pm

Re: Invoking built-in script from a webpage

Fri Sep 21, 2012 8:48 pm

I'm little bit confused when reading this topic, about if I can retrieve only errors from script run via api, or the actual script output too.
Say, I want to:
 $printRequest = new Request('/system/script/run');
 $printRequest->setArgument('number',$scriptnum);

 foreach ($client->sendSync($printRequest) as $response) {
        foreach ($response->getAllArguments() as $name => $value) {
            echo $name, ': ', $value, "\n";
        }
}
I'm getting empty result set with above when running script that normally returns data. The script is an approach to solve famous wlan scan problem with api:
:global scanresult [int wir scan number=wlan1 duration=2]; :put $scanresult
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2400
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Invoking built-in script from a webpage

Fri Sep 21, 2012 9:03 pm

The :put command shows the output on the console screen... but the console screen itself (in general) is not available to the API. Most API related issues are exactly due to that - MikroTik need to alter each command to support the API by formatting the output into a compatible fashion, and some of the more exotic commands haven't been touched up.

If "/interface/wireless/scan number=wlan1 duration=2" doesn't work, your best bet is to write the results to a file, and then download the new file, perhaps using FTP, and parse it (which will be the difficult part...).
 
riemann
just joined
Posts: 5
Joined: Wed Sep 28, 2005 8:52 pm

Re: Invoking built-in script from a webpage

Fri Sep 21, 2012 10:52 pm

Thanks. It seems actually that it's not even possible to save scan results neither to file nor to variable (which would be used to save variable to file contents next).
 
User avatar
boxybh
Frequent Visitor
Frequent Visitor
Posts: 55
Joined: Sat Jul 29, 2017 11:16 am

Re: Invoking built-in script from a webpage

Sun Jan 15, 2023 10:36 am

why dont ya let webpage make a txt file there itself and let mikrotik read that file periodically and accordingly run a script ont he values retrieved.

Who is online

Users browsing this forum: No registered users and 17 guests