Source Log parsing - admin.log, login.log and rcon.log (SOF/1fx-specific)

Source code of specific applications

Janno

why let your life be controlled by fairytales
As games.log is always the largest file, I wanted to keep that in a separate script. This script handles the rest.
Yet again, having all of this parsed, gives us access to do whatever sort of automatisation we want. For example, we have a daily post about the last day abusers and the admins who did it.

Table spec:
SQL:
CREATE TABLE abuse_adminlog (
    abuse_adminlog_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY
    , dt DATETIME
    , action VARCHAR(255)
    , byip VARCHAR(255)
    , byname VARCHAR(255)
    , toip VARCHAR(255)
    , toname VARCHAR(255)
);

CREATE TABLE abuse_loginlog (
    login_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY
    , dt DATETIME
    , player VARCHAR(64)
    , adminLevel TINYINT
    , ip VARCHAR(20)
);

CREATE TABLE abuse_rconlog (
    abuse_rconlog_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY
    , dt DATETIME
    , action VARCHAR(255)
    , ip VARCHAR(255)
);

CREATE INDEX idx_adminlog_dt ON abuse_gameslog(dt);
CREATE INDEX idx_loginlog_dt ON abuse_loginlog(dt);
CREATE INDEX idx_rconlog_dt ON abuse_rconlog(dt);

Script yet again has room for improvement and can be unreadable at parts, but it works fine.

As with games.log, the files are moved to the PHP script folder (file /root/copylogs).

Bash:
#!/bin/bash

srvPath="/home/server/"
logFolder="1fx/logs/"
logFolderRcon="1fx/logs/"
adminLogName="admin.log"
rconLogName="rcon.log"
loginLogName="login.log"

destination="/var/www/xxxx/"

cp $srvPath$logFolder$adminLogName $destination
echo -n > $srvPath$logFolder$adminLogName
cp $srvPath$logFolderRcon$rconLogName $destination
echo -n > $srvPath$logFolderRcon$rconLogName
cp $srvPath$logFolder$loginLogName $destination
echo -n > $srvPath$logFolder$loginLogName
chown www-data $destination$adminLogName
chown www-data $destination$rconLogName
chown www-data $destination$loginLogName

And the parser script itself:

PHP:
<?php
$handle = ssh2_connect("127.0.0.1", 22);
if (!ssh2_auth_password($handle, "username", "password")) {
    throw new Exception("Can't connect!");
}

$stream = ssh2_exec($handle, "/root/copylogs");
stream_set_blocking($stream, true);
stream_set_timeout($stream, 2);
$stream_err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
print_r(array("stdio" => stream_get_contents($stream), "stderr" => stream_get_contents($stream_err)));

require_once __DIR__ . "/classes/log/RCON.php";
require_once __DIR__ . "/classes/log/Admin.php";
require_once __DIR__ . "/classes/log/LoginLog.php";
require_once __DIR__ . "/classes/pdo/SQL.php";


$arr = file_get_contents(__DIR__ . "/admin.log");

$array = explode("\n", $arr);
//parse the array, swap shit


//print_r($array);
$objects = array();
foreach ($array as $line) {

    $split = preg_split("/[\s\\\\]+/", $line);
    //as this shit isn't as easy as rcon, we gotta read contents.
    if (sizeof($split) < 4) {
        continue;
    }
    //0-1 is the datetime object.
    $dt = new DateTime($split[0] . " " . $split[1]);
    //read action up until we hit "by" followed with an IP.
    $action = "";
    $i = 0;
    $byip = "";
    $byadm = 5;
    $by = "";
    for ($i = 2; $i < sizeof($split); $i++) {
        if (trim(substr(trim($split[$i]), strlen(trim($split[$i])) - 2, strlen(trim($split[$i])))) === "by" && (filter_var(trim($split[$i + 1]), FILTER_VALIDATE_IP) || trim($split[$i + 1]) === "RCON")) {
            $action .= substr(trim($split[$i]), 0, strlen(trim($split[$i])) - 2);
            $i++;
            $byip = trim($split[$i]);
            if ($byip === "RCON") {
                break;
            }
            //$i++;
            //$byadm = trim($split[$i]); this is supposed to be the admin level of the person, but that is not available on public 1fx release
            $i++;
            break;
        }
        if (trim($split[$i]) === "friendlyfire") {
            //friendlyfire isn't quite that *friendly*.
            $action = trim($split[$i]) . " ";
            $i++;
            $action .= substr(trim($split[$i]), 0, strlen(trim($split[$i])) - 2);
            $i++;
            $byip = trim($split[$i]);
            if ($byip === "RCON") {
                break;
            }
           
//$i++;
            //$byadm = trim($split[$i]); this is supposed to be the admin level of the person, but that is not available on public 1fx release
            $i++;
            break;
        }
        if (trim($split[$i]) === "by" && (filter_var(trim($split[$i + 1]), FILTER_VALIDATE_IP) || trim($split[$i + 1]) === "RCON")) {
            $i++;
            $byip = trim($split[$i]);
            if ($byip === "RCON") {
                break;
            }
           
//$i++;
            //$byadm = trim($split[$i]); this is supposed to be the admin level of the person, but that is not available on public 1fx release
            $i++;
            break; //got the action.
        }
        $action .= $split[$i] . " ";
    }
    $action = trim($action);
    if ($action === "altmap switch") {
        $action .= " " . $split[sizeof($split) - 3] . " " . $split[sizeof($split) - 2] . " " . $split[sizeof($split) - 1];
        for ($i; $i < sizeof($split) - 3; $i++) {
            $by .= $split[$i] . " ";
        }
        $by = trim($by);
        $toip = "";
        $to = "";
        $action = htmlentities($action);
        $by = htmlentities($by);
        $objects[] = new Admin($dt, $action, $byip, $by, $toip, $to, $byadm);
        continue;
    }
    //we got action, now to by.

    $toip = null;
    for ($i; $i < sizeof($split); $i++) {
        if (trim($split[$i]) === "to" && (filter_var(trim($split[$i + 1]), FILTER_VALIDATE_IP) || (trim($split[$i + 1]) === "all" && trim($split[$i + 2]) === "clients" ) || (trim($split[$i + 1]) === "AllPlayer"))) { //this case will not be reached if sizeof is too small, e.g. action not concerning a to
            $i++;
            $toip = trim($split[$i]);
            $i++;
            break; //got the action.
        }
        $by .= $split[$i] . " ";
    }
    $by = trim($by);
    $to = null;
    for ($i; $i < sizeof($split); $i++) {
        $to .= $split[$i] . " ";
    }
    $to = trim($to);
    if (($toip === null || strlen(trim($toip)) === 0) && ($to === null || strlen($to) === 0)) {
        if (trim($action) === "remove admin") {
            $nspl = preg_split("/[\s\\\\]+/", $by);
            $gotStr = false;
            $by = "";
            for ($j = 0; $j < sizeof($nspl); $j++) {
                $str = trim($nspl[$j]);
                if ($str === "to") {
                    $gotStr = true;
                    continue;
                }
                if ($gotStr) {
                    $action .= " " . $str;
                } else {
                    $by .= " " . $str;
                }
            }
            $action = trim($action);
            $by = trim($by);
        }
    }
    $objects[] = new Admin($dt, $action, $byip, $by, $toip, $to, $byadm);
}



$dsn = "mysql:host=localhost;dbname=database";
$username = "username";
$password = "password";
$sql = new SQL($dsn, $username, $password);

//as there can be shitton of rows, use transaction so if we fail, we fail with everything and don't have to figure out where we failed.
$sql->beginTransaction();
foreach ($objects as $object) {
    $sql->query("INSERT INTO abuse_adminlog (dt, action, byip, byname, toip, toname, adminlevel) VALUES (?, ?, ?, ?, ?, ?, ?)", $object->toSQLParams());
}
$sql->commit();
$objects = array();

$arr = file_get_contents(__DIR__ . "/rcon.log");

$array = explode("\n", $arr);

foreach ($array as $line) {
    $action = $ip = $dt = null;
    $split = preg_split("/[\s]+/", $line);
    if (sizeof($split) < 4) {
        continue; //probably empty line, weird shit.
    }
    //split the line so we can parse the shit out of it.
   
    $ip = trim(explode(":", $split[sizeof($split) - 1])[0]);
    for ($i = 2;$i < sizeof($split) - 2; $i++) {
        $action .= $split[$i] . " ";
    }
    $action = strtolower(trim($action));
    if ($ip === "127.0.0.1" || $ip === gethostbyname(gethostname()) || $ip === "https://xxxx.3d-sof2.com") {
        if ($action === "g_enablehash" || $action === "status") {
            continue;
        }
        $ip = "ServerPanel Web RCON";
    }
   
    //read up until len of array - 1, that should be "by"
    $dt = new DateTime($split[0] . " " . $split[1]);
    //2nd is action, read until sizeof array -2
   
   

    $objects[] = new RCON($dt, $action, $ip);
}




$stream = ssh2_exec($handle, "echo -n > /var/www/xxxx/rcon.log");
stream_set_blocking($stream, true);
stream_set_timeout($stream, 2);
$stream_err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
print_r(array("stdio" => stream_get_contents($stream), "stderr" => stream_get_contents($stream_err)));
$stream = ssh2_exec($handle, "echo -n > /var/www/xxxx/admin.log");
stream_set_blocking($stream, true);
stream_set_timeout($stream, 2);
$stream_err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
print_r(array("stdio" => stream_get_contents($stream), "stderr" => stream_get_contents($stream_err)));


$sql->beginTransaction();
foreach ($objects as $object) {
    $sql->query("INSERT INTO abuse_rconlog (dt, action, ip) VALUES (?, ?, ?)", $object->toSQLParams());
}

$sql->commit();


$objects = array();
$arr = file_get_contents(__DIR__ . "/login.log");
//die(print_r($arr));
$array = explode("\n", $arr);

foreach ($array as $line) {
    $ip = $dt = $player = $adminLevel = null;
    $split = preg_split("/[\s]+/", $line);
    if (sizeof($split) < 4) {
        continue; //probably empty line, weird shit.
    }
    //read up until len of array - 1, that should be "by"
    $dt = new DateTime($split[0] . " " . $split[1]);
    //2nd is action, read until sizeof array -2
    //ip and powers is easy.
    $ip = rtrim($split[sizeof($split) - 1], ".");
    $adminLevel = LoginLog::textToAdmin(rtrim($split[sizeof($split) - 3], "."));
    //player starts from 4th
    $player = substr($split[4], 1); //remove starting '
    for ($i = 5; $i < sizeof($split); $i++) {
        $tmp = $split[$i];
        if (trim($tmp) === "has" && sizeof($split) > ($i + 2) && $split[$i + 1] === "been" && $split[$i + 2] === "granted") {
            $player = substr($player, 0, strlen($player) - 1); //remove trailing '
            break; //got name
        }
        $player .= " $tmp";
    }
    $objects[] = new LoginLog($dt, $player, $adminLevel, $ip);
    //$objects[] = new LoginLog();
}


$stream = ssh2_exec($handle, "echo -n > /var/www/xxxx/login.log");
stream_set_blocking($stream, true);
stream_set_timeout($stream, 2);
$stream_err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
print_r(array("stdio" => stream_get_contents($stream), "stderr" => stream_get_contents($stream_err)));

$sql->beginTransaction();
foreach ($objects as $object) {
    $sql->query("INSERT INTO abuse_loginlog (dt, player, adminLevel, ip) VALUES (?, ?, ?, ?)", $object->toSQLParams());
}

$sql->commit();
ssh2_disconnect($handle);
 
Back
Top