Тема: Как сделать API из любой команды

Намедни понадобилось выполнять команды на другом сервере. Можно конечно и через SSH, но захотелось чего-то новенького. Тут просто пример, нет никакой защиты так как создано для общения двух внутренних систем где все строго ограничивается фаерволом, но добавить SSL и авторизацию с ограничением IP с которых можно посылать запросы не составит никакого труда...

Для реализации я применил PHP, не потому что я его люблю, просто когда я что-то делаю с perl'ом на меня очень криво смотрят, да и программировать на нем мало кто умеет.

Идея достаточно просто, подать команды в бинарник на удаленном сервере и полечить обратно ответ о результате выполнения. Реализация первая и последняя так как в последствии выяснилось что есть нормальный API интерфейс HTTP+XML. Но не пропадать же добру wink кому надо - пользуйтесь...

#!/usr/bin/php
<?php
/*
how to use
echo "-m module_name -o json pricelist" | base64 | awk '{print "1234567890"$1}' | nc 127.0.0.1 1001
*/
$pass = "1234567890";
$server = stream_socket_server("tcp://127.0.0.1:1001", $errno, $errorMessage);
if ($server === false) {
    throw new UnexpectedValueException("Could not bind to socket: $errorMessage");
}
while ($conn = @stream_socket_accept($server,-1)) {
    $auth = false;
    $msg=fread($conn, 1024);
    if (strlen ($msg) > 10) {
        $pass2="";
        for ($i=0; $i<10; $i++) {
            $pass2 .= $msg[$i];
        }
        if ($pass==$pass2) {
            $auth=true;
            $msg = base64_decode(substr($msg, 10));
        }
    }
    if ($auth === true) {
        exec ("/path/to/mgrctl ".$msg,$mgrctl_out);
        $back_text = "";
        foreach ($mgrctl_out as $val) {
            $back_text .= $val."\n";
        }
        stream_socket_sendto ($conn,base64_encode($back_text));
        fclose($conn);
    } else {
        stream_socket_sendto ($conn,"Auth error\n");
        fclose($conn);
    }
}
    fclose($conn);
?>

Коротко о скрипте, его надо демонезировать и повесить на любой порт/ip по вкусу. Скрипт открывает TCP сокет и принимает запросы в формате пароль+base64(команда) возвращает ответ в base64 формате.

Пример как посылать запросы

<?php
error_reporting(E_ALL);
$service_port = "1001";
$address = "111.111.111.111";
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) die ("socket_create() failed: reason: ".socket_strerror(socket_last_error())."\n");
$result = socket_connect($socket, $address, $service_port);
if ($result === false) die ("socket_connect() failed.\nReason: ($result) ".socket_strerror(socket_last_error($socket))."\n");
$in = "1234567890LW0gbW9kdWxlX25hbWUgLW8ganNvbiBwcmljZWxpc3QK\r\n";
$out = '';
socket_write($socket, $in, strlen($in));
while ($out = socket_read($socket, 2048)) {
    echo $out;
}
socket_close($socket);
?>

формируем запрос и отправляем на нужный IP и порт, получаем строку в формате base64, дальше ее нужно раскодировать и использовать...

2

Re: Как сделать API из любой команды

Спасибо!
Пригодится.

3

Re: Как сделать API из любой команды

Будем развивать тему, так как недавно аж три человека просили помощи в API, демонизации и автоматизации систем которые не имеют вообще ни какова API интерфейса. Почему API? Глупый вопрос, идем читаем что это такое и т.д и т.п.

Очень подробно не буду, так как  тема обширная и стоит на стыке большого количества технологий. То есть если у вас нет определенного багажа знаний - ковыряйте литературу!

Пример программы который я привел вполне пригоден, надо ее только правильно демонезировать, на мой взгляд самое простое и надежное - это initd или xinetd. Конечно правильнее написать все на perl (или другой адекватный язык), но это уже кому как удобно... Дальше все буду приводить на PHP так как его понимает большинство (деградируем товарищи smile )

Так же ремарка по поводу культуры программирования, возможности расширения, и удобстве - используйте class'ы!

Технически все просто, принимает REST запрос, где по потребностям получаем POST или GET (можно пойти дальше и использовать так же PUT и DELETE и сделать все в классическом варианте, но не все поддерживают это и могут возникнуть проблемы на прокси и фаерволах) формируем ответ и отдаем его в виде XML или JSON, дело вкуса... Так же при выполнении команд можно сразу выполнять, а можно отдать ID задания, запланировать выполнения и отпустить запрашивающего. Это более правильно если вы выполняете большие операции, требующие много времени. Но для этого надо сделать свой планировщик. Проще всего, это скрипт, работающий по системному cron'у. B следующий раз когда пользователь обращается и сообщает ID задания то вы ему выдаете результат выполнения, или сообщение что задание еще не выполнено.

Вот примерчик, как запланировать добавление домена на DNS сервер как только счет будет оплачен

$act = array (
    'id'=>'7',
    'system'=>'dnsmanager',
    'action'=>'adddomain',
    'item_id'=>'65',
    'domain'=>'abakakriabaka.com',
    'dns_type'=>'master',
    'ip'=>'111.111.111.112'
);
$data = json_encode($act);
$res=$system->cron_add_job(7,$data);

Я держу в mysql'e, но можно где угодно, если его нет то sqlite к примеру

public function cron_add_job ($userid,$job) {
    $db = $this->do_mysql_connect();
    $sql = "INSERT INTO `cron` (`id`, `user_id`, `task`, `status`) VALUES (NULL, $userid,'".mysql_real_escape_string($job)."', 0);";
    $res = mysql_query ($sql,$db);
...
    return ...;
}

То есть в базу у нас падает вот такая команда, из которой видно что и кому делать (это пробная версия, тут нет времени, дополнительных триггеров, более удобного поиска заданий), но для примера это даже хорошо, меньше кода.

{"id":"7","system":"dnsmanager","action":"adddomain","item_id":"65","domain":"abakakriabaka.com","dns_type":"master","ip":"111.111.111.112"}

периодически срабатывает скрипт который вытаскивает все что надо

public function cron_get_jobs () {
    $db = $this->do_mysql_connect();
    $sql = "select * from cron where status=0;";
    $res = mysql_query ($sql,$db);
    while($l = mysql_fetch_assoc($res)) {.
        if ($l['task']!='') $a[]=$l;.
        }
    return $a;
}

разбираем это все дало на составляющие

public function cron_parse_jobs ($list) {             
    $i=0;
    foreach ($list as $v) {
        $res_tm = json_decode($v['task']);
        $res[$i]['id'] = $v['id'];
        $res[$i]['user_id'] = $v['user_id'];
        $res[$i]['status'] = $v['status'];
        $res[$i]['task_user_id'] = $res_tm->id;
        $res[$i]['system'] = $res_tm->system;
        $res[$i]['action'] = $res_tm->action;
        $res[$i]['item_id'] = $res_tm->item_id;
        $res[$i]['rhost_pid'] = $res_tm->rhost_pid;
        $res[$i]['dns_pid'] = $res_tm->dns_pid;
        $res[$i]['order_id'] = $res_tm->order_id;
        $res[$i]['domain'] = $res_tm->domain;
        $res[$i]['dns_type'] = $res_tm->dns_type;
        $res[$i]['ip'] = $res_tm->ip;
        $i++;
    }
    return $res;
}

Вытаскиваем конкретные задания

public function cron_find_by_system ($list,$system) {
    foreach ($list as $v) {
        if ($v['system']==$system) {
            $res[] = $v;
        }
    }
    return $res;
}

и после выполнения записываем статус и помечаем как выполнено или ошибочно

public function cron_mark_as_completed ($id) {
    $db = $this->do_mysql_connect();
...
    $sql = "update atlax_cron set status=1 where id=".$id." limit 1;";
    $res = mysql_query ($sql,$db);
...
    return ...;
}

Вопросы принимаются.