Blocklist for specific user or group

Is there any way or idea to create a blocklist for specific user?

I’m currently working with PHP-API PEAR scripting on MikroTik with UserManager
Hosted in web server with php ver 5.3.x

I’ve done some user’s password changing feature, auto-fetch session using cron Job for reporting purpose and some other feature.
and I asked to add a feature that able to block some websites from specific user or group.
is there any idea how to do it? I was about to inject it from MikroTik’s html files, like status.html or login.html.
but apparently I can’t find any logic flow that check whether they have been logged in or not.

well actually I’m super newbie at MikroTik, but I’m good enough at PHP.. I don’t really know how mikrotik and usermanager works and its features..

any idea?

TL;DR; how to add website-block list for users using PHP API or MikroTik/UserManager itself?

thanks in advance.

You’ll have to do the filtering on a per-profile basis, as that’s the only place where you can set connection parameters like marks and the like. Of course, you can have one or more “clean” profiles, and then one or more “filtered” profiles for each user (or group) you want to apply filters on.

There’s also another more sinister thing to keep in mind - filtering is performed on an IP level, not DNS level. I’m not sure how to properly work around that limitation… not with hotspot at least.

If that is enough for you, you can specify an outgoing filter in the profile (you might want to use a custom chain name), and then add a filter rule (on the same chain name of course) that checks if the dst-address is among the ones you want filtered, and drops the packets if it is.

BTW, thanks.

[edit]
Would you be OK with a transparent PHP proxy? I.e. a dst-nat to your web server, at which, depending on $_SERVER[‘HTTP_HOST’], PHP would either download the page and output it to the client, or output an error page. Depending on the web server ip:port combo, you can make PHP use a different set of filters.

This approach would significantly slow down the speed for all sites for users with “filtered” profiles though.
[/edit]

I GOT IT!!! dns_get_record()!!!

You could use that to resolve any DNS name to all of its IPs, and then add filter rules in MikroTik for each IP. You can use the returned TTL value as the time for a scheduler job (at the router or the web server; doesn’t really matter), which would purge the entries for this domain, and set off to recreate the entries (which would ensure fresh entries).


The only potential downside to this approach is that you have to block, at minimum, all HTTP requests to matching IPs. So if the same server hosts multiple domains, users would not be able to access those other domains.

If I get around to it, I might write some code about that algorithm, but feel free to beat me to it (and if you do, I’d love to see that code).

OK, here’s an updater:

<?php
use PEAR2\Net\RouterOS as R;
require_once 'PEAR2/Autoload.php';

$taskNamePrefix = 'ROSUpdater';
$addressListPrefix = '[ROSUpdater] ';

//Deal with the command line arguments.
if (!isset($argv)) {
    die('This script must be executed from the command line.');
}
array_shift($argv);
$domain = trim((string) array_shift($argv));
$lists = $argv;

//Verify required arguments.
if ('' === $domain) {
    die('You must specify a domain as a first argument.');
}

$client = new R\Client('ros.example.com', 'apifull', 'apifull');

//Purge old entries for this domain (for all lists!!!).
$request = new R\Request('/ip firewall address-list print .proplist=.id');
$idList = '';
foreach ($client->sendSync($request->setQuery(R\Query::where('comment', $addressListPrefix . $domain))) as $response) {
    $idList .= $response->getArgument('.id') . ',';
}
$idList = rtrim($idList, ',');
if (!empty($idList)) {
    $request = new R\Request('/ip firewall address-list remove');
    $client->sendSync($request->setArgument('numbers', $idList));
}

if (empty($lists)) {
    //
    // Windows specific.
    //
    exec('schtasks /Delete /F /TN ' . escapeshellarg($taskNamePrefix . '\\' . $domain));
    exec('schtasks /Query /FO TABLE /TN ' . escapeshellarg($taskNamePrefix . '\\\\'), $output);
    if ('' === trim(implode('', $output))) {
        exec('schtasks /Delete /F /TN ' . escapeshellarg($taskNamePrefix));
    }
    
    die('Domain purged. Tasks cleared. Nothing to add.');
}

//Add all IPs to each list.
$request = new R\Request('/ip firewall address-list add');
$request->setArgument('comment', $addressListPrefix . $domain);
foreach (gethostbynamel($domain) as $ip) {
    $request->setArgument('address', $ip);
    foreach ($lists as $list) {
        $client->sendSync($request->setArgument('list', $list));
    }
}

//Get reccomended refresh interval from DNS server.
$refreshAfter = dns_get_record($domain, DNS_SOA);
$refreshAfter = $refreshAfter[0]['refresh'];

//Calculate next refresh.
$updateTime = new DateTime("now +{$refreshAfter} seconds");
$updateTime = $updateTime->format('c');

//Generate the arguments string for the task.
$arguments = '-f ' . escapeshellarg(__FILE__) . ' -- ' . escapeshellarg($domain) . ' ';
foreach ($lists as $list) {
    $arguments .= escapeshellarg($list) . ' ';
}
$arguments = rtrim($arguments, ' ');

//
// Everything below is applicable only to Windows, for the purpose of creating a scheduled task.
// Adjust accordingly for Cron.
//

//Save the XML file temporarily, so that we can create a task from it.
$resultPath = __DIR__ . DIRECTORY_SEPARATOR . $domain . '.xml';
file_put_contents($resultPath,
<<<HEREDOC
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers>
    <TimeTrigger>
      <StartBoundary>{$updateTime}</StartBoundary>
      <EndBoundary>{$updateTime}</EndBoundary>
      <Enabled>true</Enabled>
    </TimeTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>true</WakeToRun>
    <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
    <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>"D:\PHP\php.exe"</Command>
      <Arguments>{$arguments}</Arguments>
    </Exec>
  </Actions>
</Task>
HEREDOC
);
exec('schtasks /Create /RU SYSTEM /RP "" /F /XML ' . escapeshellarg($resultPath) . ' /TN ' . escapeshellarg($taskNamePrefix . '\\' . $domain));
unlink($resultPath);

//If you can read that, without anything else on screen, chances are there are no errors.
echo 'OK';

After adjusting your router details and PHP location in the XML, the way you use it is from the command line (running as Administrator!!!), as

php -f file -- domain [list [...]]

e.g.

D:\PHP\php.exe -f updater.php -- google.com profile1-filters profile2-filters

will add “google.com” to the address lists “profile1-filters” and “profile2-filters”.

You can clear entries for a domain (and its scheduled task) by running without specifying lists, e.g.

D:\PHP\php.exe -f updater.php -- google.com

to purge google.com entries.

Previously, at the router, you’d have to set user profiles like

/ip hotspot user profile add name=profile1 address-list=profile1

and a corresponding filter rule (one per profile) like

/ip firewall filter add protocol=tcp dst-port=80 src-address-list=profile1 dst-address-list=profile1-filters chain=forward action=drop

or if you want to show an error page, you could use a NAT rule instead of a filter rule, like:

/ip firewall nat add protocol=tcp dst-port=80 src-address-list=profile1 dst-address-list=profile1-filters chain=dstnat action=dst-nat to-address=192.168.0.254 to-ports=80

(replacing 192.168.0.254 with the IP of your web server, and adjusting the port if necessary)


As designed, it currently works only with Windows’ scheduler, but you can easily adjust it for UNIX’s cron instead.