api.routeros.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /**
  3. * Mikrotik API implementation
  4. */
  5. class RouterOS {
  6. // Socket resource:
  7. private $socket;
  8. private $port;
  9. // Public variables:
  10. public $connected = false;
  11. public $debug_str = null;
  12. public $error_str = null;
  13. public $error_num = null;
  14. // Constatns of class:
  15. //const PORT = 8728;
  16. const DEBUG = false;
  17. const ATTEMPTS = 3;
  18. const TIMEOUT = 5;
  19. const DELAY = 0;
  20. /**
  21. * Fills in the `$this->debug_str` variable if self::DEBUG is enabled
  22. *
  23. * @param string $string Debug message string
  24. * @return boolean
  25. */
  26. private function debug($string) {
  27. if ( self::DEBUG ) {
  28. $this->debug_str .= $string . '<br />' . PHP_EOL;
  29. }
  30. }
  31. /**
  32. * Establishes connection with RouterOS device via API
  33. *
  34. * @param string $hostname Device`s IP or hostname
  35. * @param string $username Username
  36. * @param string $password Password
  37. * @param bool $UseNewConnMode
  38. * @param string $apiPort
  39. *
  40. * @return boolean $this->connected
  41. */
  42. public function connect($hostname, $username, $password, $UseNewConnMode = false, $apiPort = '8728') {
  43. // Connect to device:
  44. for ( $attempt = 1; $attempt <= self::ATTEMPTS; $attempt++ ) {
  45. $this->connected = false;
  46. $this->port = $apiPort;
  47. $this->debug('Connection attempt #' . $attempt . ' to ' . $hostname . ':' . $this->port);
  48. $this->socket = @fsockopen($hostname, $this->port, $this->error_num, $this->error_str, self::TIMEOUT);
  49. if ( $this->socket ) {
  50. socket_set_timeout($this->socket, self::TIMEOUT);
  51. if ($UseNewConnMode) {
  52. $this->write('/login', false);
  53. $this->write('=name=' . $username, false);
  54. $this->write('=password=' . $password);
  55. $response = $this->read(false);
  56. if (isset($response[0]) && $response[0] == '!done') {
  57. $this->connected = true;
  58. break;
  59. }
  60. } else {
  61. $this->write('/login');
  62. $response = $this->read(false);
  63. if (isset($response[0]) && $response[0] == '!done') {
  64. if (preg_match_all('/[^=]+/i', $response[1], $matches)) {
  65. if ($matches[0][0] == 'ret' && strlen($matches[0][1]) == 32) {
  66. $this->write('/login', false);
  67. $this->write('=name=' . $username, false);
  68. $this->write('=response=00' . md5(chr(0) . $password . pack('H*', $matches[0][1])));
  69. $response = $this->read(false);
  70. if (isset($response[0]) && $response[0] == '!done') {
  71. $this->connected = true;
  72. break;
  73. }
  74. }
  75. }
  76. }
  77. }
  78. fclose($this->socket);
  79. }
  80. sleep(self::DELAY);
  81. }
  82. // Throw debug-message:
  83. if ( $this->connected ) {
  84. $this->debug('Connection with device is established...');
  85. } else {
  86. $this->debug('Couldn`t establish connection with device!');
  87. }
  88. // Return connection state:
  89. return $this->connected;
  90. }
  91. /**
  92. * Encodes length
  93. *
  94. * @param string $length Length to encode
  95. * @return string
  96. */
  97. private function encode_length($length) {
  98. switch ( true ) {
  99. case ( $length < 0x80 ):
  100. $length = chr($length);
  101. break;
  102. case ( $length < 0x4000 ):
  103. $length |= 0x8000;
  104. $length = chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
  105. break;
  106. case ( $length < 0x200000 ):
  107. $length |= 0xC00000;
  108. $length = chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
  109. break;
  110. case ( $length < 0x10000000 ):
  111. $length |= 0xE0000000;
  112. $length = chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
  113. break;
  114. case ( $length >= 0x10000000 ):
  115. $length = chr(0xF0) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
  116. break;
  117. }
  118. return $length;
  119. }
  120. /**
  121. * Parses RouterOS device`s reply to 'comfortable' array
  122. *
  123. * @param array $response RouterOS device`s reply
  124. * @return array
  125. */
  126. private function parse_response($response) {
  127. $return = array();
  128. if ( is_array($response) ) {
  129. $current = null;
  130. $single = null;
  131. foreach ( $response as $key ) {
  132. if ( in_array($key, array('!fatal', '!re', '!trap')) ) {
  133. if ( $key == '!re' ) {
  134. $current = & $return[];
  135. } else $current = & $return[$key][];
  136. } else if ( $key != '!done' ) {
  137. if ( preg_match_all('/[^=]+/i', $key, $matches) ) {
  138. if ( $matches[0][0] == 'ret' ) {
  139. $single = $matches[0][1];
  140. }
  141. $current[$matches[0][0]] = ( isset($matches[0][1] ) ? $matches[0][1] : null);
  142. }
  143. }
  144. }
  145. if ( empty($return) && !is_null($single) ) {
  146. $return = $single;
  147. }
  148. }
  149. return $return;
  150. }
  151. /**
  152. * Reads data from RouterOS device via API
  153. *
  154. * @param boolean $parse_response
  155. * @return array
  156. */
  157. public function read($parse_response = true) {
  158. $response = array();
  159. $_ = '';
  160. $_done = false;
  161. while ( true ) {
  162. $byte = ord(fread($this->socket, 1));
  163. $length = 0;
  164. if ($byte & 128) {
  165. if (($byte & 192) == 128) {
  166. $length = (($byte & 63) << 8) + ord(fread($this->socket, 1));
  167. } else {
  168. if (($byte & 224) == 192) {
  169. $length = (($byte & 31) << 8) + ord(fread($this->socket, 1));
  170. $length = ($length << 8) + ord(fread($this->socket, 1));
  171. } else {
  172. if (($byte & 240) == 224) {
  173. $length = (($byte & 15) << 8) + ord(fread($this->socket, 1));
  174. $length = ($length << 8) + ord(fread($this->socket, 1));
  175. $length = ($length << 8) + ord(fread($this->socket, 1));
  176. } else {
  177. $length = ord(fread($this->socket, 1));
  178. $length = ($length << 8) + ord(fread($this->socket, 1));
  179. $length = ($length << 8) + ord(fread($this->socket, 1));
  180. $length = ($length << 8) + ord(fread($this->socket, 1));
  181. }
  182. }
  183. }
  184. } else $length = $byte;
  185. if ($length > 0) {
  186. $retlen = 0;
  187. $_ = '';
  188. while ($retlen < $length) {
  189. $toread = $length - $retlen;
  190. $_ .= fread($this->socket, $toread);
  191. $retlen = strlen($_);
  192. }
  193. $response[] = $_;
  194. $this->debug('>>> [' . $retlen . '/' . $length . '] bytes read.');
  195. }
  196. if ($_ == '!done') { $_done = true; }
  197. $status = socket_get_status($this->socket);
  198. if ($length > 0) {$this->debug('>>> [' . $length . ', ' . $status['unread_bytes'] . ']' . $_);}
  199. if ( (!$this->connected && !$status['unread_bytes']) || ($this->connected && !$status['unread_bytes'] && $_done) ) {
  200. break;
  201. }
  202. }
  203. return ( $parse_response ) ? $this->parse_response($response) : $response;
  204. }
  205. /**
  206. * Writes command to RouterOS device via API
  207. *
  208. * @param string $command Sending command's string
  209. * @param boolean $param ...
  210. * @return boolean
  211. */
  212. public function write($command, $param = true) {
  213. if ( $command ) {
  214. $data = explode('\n', $command);
  215. foreach ( $data as $cmd ) {
  216. $cmd = trim($cmd);
  217. fwrite($this->socket, $this->encode_length(strlen($cmd)) . $cmd);
  218. $this->debug('<<< [' . strlen($cmd) . '] ' . $cmd);
  219. }
  220. $type = gettype($param);
  221. switch ( $type) {
  222. case 'integer':
  223. fwrite($this->socket, $this->encode_length(strlen('.tag=' . $param)) . '.tag=' . $param . chr(0));
  224. $this->debug('<<< [' . strlen('.tag=' . $param) . '] .tag=' . $param);
  225. break;
  226. case 'boolean':
  227. fwrite($this->socket, ($param ? chr(0) : ''));
  228. break;
  229. }
  230. }
  231. return ( $command ) ? true : false;
  232. }
  233. /**
  234. * Use it for sending commands to RouterOS device
  235. *
  236. * @param string $string RouterOS`s command string
  237. * @param array $data Command`s parameters
  238. * @return array
  239. */
  240. public function command($string, $data = array()) {
  241. $count = count($data);
  242. $this->write($string, !$data);
  243. $i = 0;
  244. foreach ( $data as $key => $value ) {
  245. switch ( $key[0] ) {
  246. case '?':
  247. $el = ($key[1] == '#') ? $key . $value : $key . '=' . $value;
  248. break;
  249. case '~':
  250. $el = $key . '~' . $value;
  251. break;
  252. default:
  253. $el = '=' . $key . '=' . $value;
  254. break;
  255. }
  256. $last = ($i++ == $count - 1);
  257. $this->write($el, $last);
  258. }
  259. return $this->read();
  260. }
  261. /**
  262. * Closes up established connection with RouterOS device
  263. */
  264. public function disconnect() {
  265. $this->debug('Closing up established connection with RouterOS device..');
  266. $this->connected = ( !fclose($this->socket) ) ? true : false;
  267. return !$this->connected;
  268. }
  269. /**
  270. * Tries to get RouterOS version via SNMP or from login WEB page
  271. *
  272. * @param $Hostname
  273. * @param int $WEBPort
  274. * @param string $SNMPCommunity
  275. * @return float
  276. */
  277. public function determineVersion($Hostname, $WEBPort = 80, $SNMPCommunity = 'public') {
  278. $Version = '';
  279. // trying to get version via SNMP
  280. $SNMP = new SNMPHelper();
  281. $SNMP->setMode('native');
  282. $OID = '.1.3.6.1.4.1.14988.1.1.4.4.0';
  283. $tmpSNMP = $SNMP->walk($Hostname, $SNMPCommunity, $OID, false);
  284. $Version = ( empty($tmpSNMP) || $tmpSNMP === "$OID = " ) ? '' : str_replace('"', '', trim( substr($tmpSNMP, stripos($tmpSNMP, ':') + 1) ));
  285. // if first option failed - trying to parse login WEB page
  286. if ( empty($Version) ) {
  287. $curl = curl_init();
  288. curl_setopt($curl, CURLOPT_URL, $Hostname);
  289. curl_setopt($curl, CURLOPT_PORT, $WEBPort);
  290. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
  291. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  292. $Result = curl_exec($curl);
  293. curl_close($curl);
  294. if ( !empty($Result) ) {
  295. preg_match('/RouterOS v(.*?)</', $Result, $Match);
  296. if ( isset($Match[1]) && !empty($Match[1]) ) { $Version = $Match[1]; }
  297. }
  298. }
  299. return floatval($Version);
  300. }
  301. }