Ngoprek MikroTik Api Client Berbasis Python


Okeh, jika anda ingin menjadi pengembang MikroTik API client sepertinya anda perlu mencoba MikroTik API client berbasis python untuk melakukan reverse engineering. Langkah ini diperlukan untuk mengetahui setiap inputan dan hasilnya…

Okeh script ini ane ambil dari http://wiki.mikrotik.com/wiki/Manual:API pada bagian paling bawah, berikut ini  scriptnya :

#!/usr/bin/python

import sys, posix, time, md5, binascii, socket, select

class ApiRos:
    "Routeros api"
    def __init__(self, sk):
        self.sk = sk
        self.currenttag = 0

    def login(self, username, pwd):
        for repl, attrs in self.talk(["/login"]):
            chal = binascii.unhexlify(attrs['=ret'])
        md = md5.new()
        md.update('\x00')
        md.update(pwd)
        md.update(chal)
        self.talk(["/login", "=name=" + username,
                   "=response=00" + binascii.hexlify(md.digest())])

    def talk(self, words):
        if self.writeSentence(words) == 0: return
        r = []
        while 1:
            i = self.readSentence();
            if len(i) == 0: continue
            reply = i[0]
            attrs = {}
            for w in i[1:]:
                j = w.find('=', 1)
                if (j == -1):
                    attrs[w] = ''
                else:
                    attrs[w[:j]] = w[j+1:]
            r.append((reply, attrs))
            if reply == '!done': return r

    def writeSentence(self, words):
        ret = 0
        for w in words:
            self.writeWord(w)
            ret += 1
        self.writeWord('')
        return ret

    def readSentence(self):
        r = []
        while 1:
            w = self.readWord()
            if w == '': return r
            r.append(w)

    def writeWord(self, w):
        print "<<< " + w         self.writeLen(len(w))         self.writeStr(w)     def readWord(self):         ret = self.readStr(self.readLen())         print ">>> " + ret
        return ret

    def writeLen(self, l):
        if l < 0x80:
            self.writeStr(chr(l))
        elif l < 0x4000:             l |= 0x8000             self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x200000:             l |= 0xC00000             self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x10000000:             l |= 0xE0000000             self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        else:
            self.writeStr(chr(0xF0))
            self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))

    def readLen(self):
        c = ord(self.readStr(1))
        if (c & 0x80) == 0x00:
            pass
        elif (c & 0xC0) == 0x80:
            c &= ~0xC0
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xE0) == 0xC0:
            c &= ~0xE0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF0) == 0xE0:
            c &= ~0xF0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF8) == 0xF0:
            c = ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        return c

    def writeStr(self, str):
        n = 0;
        while n < len(str):
            r = self.sk.send(str[n:])
            if r == 0: raise RuntimeError, "connection closed by remote end"
            n += r

    def readStr(self, length):
        ret = ''
        while len(ret) < length:
            s = self.sk.recv(length - len(ret))
            if s == '': raise RuntimeError, "connection closed by remote end"
            ret += s
        return ret

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((sys.argv[1], 8728))
    apiros = ApiRos(s);
    apiros.login(sys.argv[2], sys.argv[3]);

    inputsentence = []

    while 1:
        r = select.select([s, sys.stdin], [], [], None)
        if s in r[0]:
            # something to read in socket, read sentence
            x = apiros.readSentence()

        if sys.stdin in r[0]:
            # read line from input and strip off newline
            l = sys.stdin.readline()
            l = l[:-1]

            # if empty line, send sentence and start with new
            # otherwise append to input sentence
            if l == '':
                apiros.writeSentence(inputsentence)
                inputsentence = []
            else:
                inputsentence.append(l)

if __name__ == '__main__':
    main()

Simpan file diatas dengan nama routeros.py, dan untuk menjalankan nya masukan perintah berikut ini :

chmod 777 routeros.py
python routeros.py 192.168.88.1 admin admin

jika login anda benar maka akan muncul hasil seperti dibawah ini :

<<< /login
<<<  >>> !done
>>> =ret=3febe34367f76733cbece5c7822ebf7c
>>>
<<< /login
<<< =name=admin
<<< =response=0043af609b070655d06d5c1a7984377b6f
<<<  >>> !done
>>>

dan jika password anda salah maka akan tampil pesan berikut ini :

<<< /login
<<<  >>> !done
>>> =ret=8b5b3964ffd9422d920b6497960bfddd
>>>
<<< /login
<<< =name=admin
<<< =response=00f3f3219cdea9fc3861b19551f3457af6
<<<  >>> !trap
>>> =message=cannot log in
>>>
>>> !done
>>>

Okey, sekarang mari mencoba menampilakan semua interface pada mikrotik dengan MikroTik API client berbasis python ini. Ketik perintah dibawah ini dan ketik enter :

/interface/print

pada laptop ane muncul nya seperti ini,:

/interface/print

<<< /interface/print
<<<  >>> !re
>>> =.id=*1
>>> =comment=
>>> =name=ether1
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*2
>>> =comment=
>>> =name=ether2
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*3
>>> =comment=
>>> =name=ether3
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*4
>>> =comment=
>>> =name=ether4
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*5
>>> =comment=
>>> =name=ether5
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*6
>>> =comment=
>>> =name=vlan1
>>> =mtu=1500
>>> =type=vlan
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*7
>>> =comment=
>>> =name=vrrp1
>>> =mtu=1500
>>> =type=vrrp
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*8
>>> =comment=
>>> =name=vlan2
>>> =mtu=1500
>>> =type=vlan
>>> =dynamic=false
>>> =running=true
>>>
>>> !done
>>>

bagaimana dengan laptop anda…..??? pengen tau, coba aja di ketik …
Okey, kalau laptop ane, ada banyak interface yang sudah di add, jadi untuk memilih interface secara spesifik ketik perintah berikut :

/interface/print
?=type=vlan

Hasilnya anda akan menampilkan interface yang type vlan saja, berikut ini hasilnya :

/interface/print
?=type=vlan

<<< /interface/print
<<< ?=type=vlan
<<<  >>> !re
>>> =.id=*6
>>> =comment=
>>> =name=vlan1
>>> =mtu=1500
>>> =type=vlan
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*8
>>> =comment=
>>> =name=vlan2
>>> =mtu=1500
>>> =type=vlan
>>> =dynamic=false
>>> =running=true
>>>
>>> !done
>>>

Okeh, sekarang bagaimana kalau ingin menampilkan interface ether1 dan ether2? coba ketik perintah berikut ini :

/interface/print
?=name=ether1
?=name=ether2
?#|

Hasilnya akan seperti ini :

<<< /interface/print
<<< ?=name=ether1
<<< ?=name=ether2
<<< ?#|
<<<  >>> !re
>>> =.id=*1
>>> =comment=
>>> =name=ether1
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*2
>>> =comment=
>>> =name=ether2
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !done
>>>

Okeh, sekali lagi, bagaimana kalau ingin menampilkan interface ether saja dan selain daripada ether1 dan ether2? coba ketik perintah ini :

/interface/print
?=type=ether
?#&
?=name=ether1
?#!
?=name=ether2
?#!

maka hasilnya akan seperti dibawah ini :

<<< /interface/print
<<< ?=type=ether
<<< ?#&
<<< ?=name=ether1
<<< ?#!
<<< ?=name=ether2
<<< ?#!
<<<  >>> !re
>>> =.id=*3
>>> =comment=
>>> =name=ether3
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*4
>>> =comment=
>>> =name=ether4
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !re
>>> =.id=*5
>>> =comment=
>>> =name=ether5
>>> =mtu=1500
>>> =type=ether
>>> =dynamic=false
>>> =running=true
>>>
>>> !done
>>>

Mengapa hasilnya bisa seperti itu ??? bagaimana rumusan nya??? Topik apa yang sedang kita bahas saat ini???
Okey, jadi yang sedang ane uji coba sekarang ini adalah tentang MikroTik API bagian Queries yang hanya disupport pada RouterOS versi 3.22 ke atas…
begini rumusan nya (walaupun sebenarnya ane masih belum terlalu faham betul, but ini asumsi ane okeh!!)

kalau berdasarkan situs http://wiki.mikrotik.com/wiki/Manual:API, berikut keterangan nya:
Okeh, jadi kalau di terjemahkan (pake google translate and di perbaiki) :

QUERIES
perintah cetak menerima kata-kata yang telah di kirimkan dan akan mengatur ulang satu set kalimat . Fitur ini tersedia sejak RouterOS 3.21.

Query dimulai dengan tanda ‘?’ (tanpa tanda petik)
Urutan kata – kata Query sifatnya signifikan. Query di evaluasi mulai dari kata pertama.
Query akan di evaluasi pertiap item yang berada dalam daftar, jika query berhasil, item akan diproses dan jika query gagal item akan diabaikan.
Query di evaluasi menggunakan tumpukan (stack) nilai boolean (true/false). Awalnya tumpukan (stack) berisi jumlah nilai ‘true’ tak terbatas. Pada akhir proses evaluasi, jika tumpukan (stack) berisi setidaknya satu nilai ‘false’ maka query menjadi gagal.
Pengoperasian kata – kata dalam query adalah berdasar pada aturan berikut ini :

Query Desciption
?name Stack akan mengirim ‘true’ jika item mempunyai value (nilai) dari property name, dan Stack akan mengirim ‘false’ jika hal sebaliknya terjadi.
?-name Stack akan mengirim ‘true’ jika item tidak memiliki value (nilai) dari property name, dan Stack akan mengirim ‘false’ jika hal sebaliknya terjadi.
?name=x
?=name=x
Stack akan mengirim ‘true’ jika property name memiliki value (nilai) sama dengan x, dan Stack akan mengirim ‘false’ jika hal sebaliknya terjadi.
?<name=x Stack akan mengirim ‘true’ jika property name memiliki nilai kurang dari x, dan Stack akan mengirim ‘false’ jika hal sebaliknya terjadi.
?>name=x Stack akan mengirim ‘true’ jika property name lebih besar dari x, dan Stack akan mengirim ‘false’ jika hal sebaliknya terjadi.
?#operations berikut ini aturan operasi yang berlaku dalam stack: string operasi di evaluasi dari kiri ke kanan.

urutan digit desimal di ikuti dengan sembarang karakter atau akhir kata ditafsirkan sebagai index stack dimana nilai index pada stack pertama adalah 0.

index  yang di ikuti oleh karakter akan mendorong salinan (copy) pada nilai tersebut. index yang di ikuti kata akhiran akan menggantikan semua nilai dengan nilai pada index tersebut.

‘!’ (not) karakter akan menggantikan nilai atas dengan nilai yang berlawanan dengan nya.

‘&’ (and) karakter akan membuang dua nilai (value) dan sebagai hasilnya karakter ini akan melakukan operasi logis ‘and’.

‘|’ (or) karakter akan membuang dua nilai (value) dan sebagai hasilnya karakter ini akan melakukan operasi logis ‘or’.

‘.’ (dot) karakter tidak akan menjadikan error

‘.’ (dot) karakter setelah karakter lain akan mendorong atau mengirim salinan pada nilai di bagian atas.

Audience : Wah masih bingung gan???
Penulis : Tenang ane juga bingung kok, abis bahasanya baku bangets… tapi mari kita bahas sama – sama siapa tau kita bisa mengerti…
jadi begini, misalnya ada sintaks seperti ini:

/interface/print
?=type=vlan
?=type=ether
?#|
?=name=ether1
?#!
?#&

kemudian misalnya, kita anggap masing – masing baris dengan kode seperti ini :
?=type=vlan adalah P1
?=type=ether adalah P2
?#| adalah L1
?=name=ether1 adalah P3
?#! adalah L2
?#& adalah L3

okey kalau kita susun dalam bentuk ke kanan jadinya seperti ini :
P1 P2 L1 P3 L2 L3

seperti yang telah dijelaskan dalam tabel diatas, setiap operasi yang ditandai dengan ?# maka dia memiliki rumusan sendiri. Dan setelah ane analisa maka bentuk tadi akan diubah menjadi seperti ini :
P1 L1 P2 L3 L2 P3

dan hasilnya akan menjadi seperti sintaks dibawah ini saat dibaca oleh MikroTik RouterOS :

?=type=vlan
?#|
?=type=ether
?#&
?#!
?=name=ether1

sehingga seolah – olah sintaks yang diterima oleh mikrotik menjadi seperti ini :

?=type=vlan ?#| ?=type=ether ?#& ?#! ?=name=ether1

masih belibet kan??? sama… tapi mari coba ubah bentuknya agar lebih mudah dimengerti menjadi seperti ini :

type=vlan or type=ether and name!=ether1

Pertanyaan nya adalah bagaimana bentuk seperti ini bisa di analogikan???
Hal ini karena mikrotik mengimplementasikan stack, yang mana setiap operasi logika and dan or akan membuang dua perintah dari stack dan akan mengubahnya menjadi bentuk operasi logis.
Nah itu asumsi ane… jadi benar atau salahnya masih belum bisa ane pertanggung jawabkan, untuk mendapat contoh – contoh yang lebih banyak silahkan buka link pada wiki mikrotik ini : http://wiki.mikrotik.com/wiki/API_command_notes

Okeh, semoga bermanfaat… sekian dan terima kasih…