├── .gitignore ├── README.rst ├── RosAPI.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.geany 3 | *.wpr 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | What is python-rosapi? 3 | ==== 4 | 5 | pythoun-rosapi is python binding on [[Mikrotik RouterOS|http://www.mikrotik.com]] API. 6 | It is generally based on Mikrotik built-in API which can be used for building your own version of Winbox, graphical configuration application for RouterOS. Basically, all RouterOS functionality can be controlled with this application. 7 | -------------------------------------------------------------------------------- /RosAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # RosAPI.py 5 | # 6 | # Copyright 2010 David Jelić 7 | # Copyright 2010 Luka Blašković 8 | # 9 | 10 | """Python binding for Mikrotik RouterOS API""" 11 | __all__ = ["RosAPICore", "Networking"] 12 | 13 | class Core: 14 | """Core part of Router OS API 15 | 16 | It contains methods necessary to extract raw data from the router. 17 | If object is instanced with DEBUG = True parameter, it runs in verbosity mode. 18 | 19 | Core part is taken mostly from http://wiki.mikrotik.com/wiki/Manual:API#Example_client.""" 20 | 21 | def __init__(self, hostname, port=8728, DEBUG=False): 22 | import socket 23 | self.DEBUG = DEBUG 24 | self.hostname = hostname 25 | self.port = port 26 | self.currenttag = 0 27 | self.sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | self.sk.connect((self.hostname, self.port)) 29 | 30 | def print_debug(self, *args): 31 | if self.DEBUG: 32 | print(args) 33 | 34 | def login(self, username, pwd): 35 | import binascii 36 | from hashlib import md5 37 | 38 | chal = None 39 | for repl, attrs in self.talk(["/login", "=name=" + username, "=password=" + pwd]): 40 | if repl == '!trap': 41 | self.print_debug(" Authentication: failed") 42 | return False 43 | elif '=ret' in attrs.keys(): 44 | chal = binascii.unhexlify(attrs['=ret']) 45 | elif repl == '!done': 46 | self.print_debug(" Authentication: done") 47 | return True 48 | else: 49 | self.print_debug(" Authentication: failed") 50 | return False 51 | md = md5() 52 | md.update('\x00') 53 | md.update(pwd) 54 | md.update(chal) 55 | for repl2, attrs2 in self.talk(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest())]): 56 | if repl2 == '!trap': 57 | self.print_debug(" Authentication: failed") 58 | return False 59 | elif repl2 == '!done': 60 | self.print_debug(" Authentication: done") 61 | return True 62 | self.print_debug(" Authentication: unknown") 63 | return False 64 | 65 | def talk(self, words): 66 | if self.writeSentence(words) == 0: return 67 | r = [] 68 | while 1: 69 | i = self.readSentence(); 70 | if len(i) == 0: continue 71 | reply = i[0] 72 | attrs = {} 73 | for w in i[1:]: 74 | j = w.find('=', 1) 75 | if (j == -1): 76 | attrs[w] = '' 77 | else: 78 | attrs[w[:j]] = w[j+1:] 79 | r.append((reply, attrs)) 80 | if reply == '!done': return r 81 | 82 | def writeSentence(self, words): 83 | ret = 0 84 | for w in words: 85 | self.writeWord(w) 86 | ret += 1 87 | self.writeWord('') 88 | return ret 89 | 90 | def readSentence(self): 91 | r = [] 92 | while 1: 93 | w = self.readWord() 94 | if w == '': return r 95 | r.append(w) 96 | 97 | def writeWord(self, w): 98 | self.print_debug("<<< " + w) 99 | self.writeLen(len(w)) 100 | self.writeStr(w) 101 | 102 | def readWord(self): 103 | ret = self.readStr(self.readLen()) 104 | self.print_debug(">>> " + ret) 105 | return ret 106 | 107 | def writeLen(self, l): 108 | if l < 0x80: 109 | self.writeStr(chr(l)) 110 | elif l < 0x4000: 111 | l |= 0x8000 112 | self.writeStr(chr((l >> 8) & 0xFF)) 113 | self.writeStr(chr(l & 0xFF)) 114 | elif l < 0x200000: 115 | l |= 0xC00000 116 | self.writeStr(chr((l >> 16) & 0xFF)) 117 | self.writeStr(chr((l >> 8) & 0xFF)) 118 | self.writeStr(chr(l & 0xFF)) 119 | elif l < 0x10000000: 120 | l |= 0xE0000000 121 | self.writeStr(chr((l >> 24) & 0xFF)) 122 | self.writeStr(chr((l >> 16) & 0xFF)) 123 | self.writeStr(chr((l >> 8) & 0xFF)) 124 | self.writeStr(chr(l & 0xFF)) 125 | else: 126 | self.writeStr(chr(0xF0)) 127 | self.writeStr(chr((l >> 24) & 0xFF)) 128 | self.writeStr(chr((l >> 16) & 0xFF)) 129 | self.writeStr(chr((l >> 8) & 0xFF)) 130 | self.writeStr(chr(l & 0xFF)) 131 | 132 | def readLen(self): 133 | c = ord(self.readStr(1)) 134 | if (c & 0x80) == 0x00: 135 | pass 136 | elif (c & 0xC0) == 0x80: 137 | c &= ~0xC0 138 | c <<= 8 139 | c += ord(self.readStr(1)) 140 | elif (c & 0xE0) == 0xC0: 141 | c &= ~0xE0 142 | c <<= 8 143 | c += ord(self.readStr(1)) 144 | c <<= 8 145 | c += ord(self.readStr(1)) 146 | elif (c & 0xF0) == 0xE0: 147 | c &= ~0xF0 148 | c <<= 8 149 | c += ord(self.readStr(1)) 150 | c <<= 8 151 | c += ord(self.readStr(1)) 152 | c <<= 8 153 | c += ord(self.readStr(1)) 154 | elif (c & 0xF8) == 0xF0: 155 | c = ord(self.readStr(1)) 156 | c <<= 8 157 | c += ord(self.readStr(1)) 158 | c <<= 8 159 | c += ord(self.readStr(1)) 160 | c <<= 8 161 | c += ord(self.readStr(1)) 162 | return c 163 | 164 | def writeStr(self, str): 165 | n = 0; 166 | while n < len(str): 167 | r = self.sk.send(str[n:]) 168 | if r == 0: raise RuntimeError, "connection closed by remote end" 169 | n += r 170 | 171 | def readStr(self, length): 172 | ret = '' 173 | while len(ret) < length: 174 | s = self.sk.recv(length - len(ret)) 175 | if s == '': raise RuntimeError, "connection closed by remote end" 176 | ret += s 177 | return ret 178 | 179 | def response_handler(self, response): 180 | """Handles API response and remove unnessesary data""" 181 | 182 | # if respons end up successfully 183 | if response[-1][0] == "!done": 184 | r = [] 185 | # for each returned element 186 | for elem in response[:-1]: 187 | # if response is valid Mikrotik returns !re, if error !trap 188 | # before each valid element, there is !re 189 | if elem[0] == "!re": 190 | # take whole dictionary of single element 191 | element = elem[1] 192 | # with this loop we strip equals in front of each keyword 193 | for att in element.keys(): 194 | element[att[1:]] = element[att] 195 | element.pop(att) 196 | # collect modified data in new array 197 | r.append(element) 198 | return r 199 | 200 | def run_interpreter(self): 201 | import select, sys 202 | inputsentence = [] 203 | 204 | while 1: 205 | r = select.select([self.sk, sys.stdin], [], [], None) 206 | if self.sk in r[0]: 207 | # something to read in socket, read sentence 208 | x = self.readSentence() 209 | 210 | if sys.stdin in r[0]: 211 | # read line from input and strip off newline 212 | l = sys.stdin.readline() 213 | l = l[:-1] 214 | 215 | # if empty line, send sentence and start with new 216 | # otherwise append to input sentence 217 | if l == '': 218 | self.writeSentence(inputsentence) 219 | inputsentence = [] 220 | else: 221 | inputsentence.append(l) 222 | return 0 223 | 224 | class Networking(Core): 225 | """Handles network part of Mikrotik Router OS 226 | 227 | Contains functions for pulling informations about interfaces, 228 | routes, wireless registrations, etc.""" 229 | 230 | def get_all_interfaces(self): 231 | """Pulls out all available data related to network interfaces""" 232 | 233 | word = ["/interface/print"] 234 | response = Core.talk(self, word) 235 | response = Core.response_handler(self, response) 236 | return response 237 | 238 | def test(): 239 | tik = Core("172.16.1.1", DEBUG=True) 240 | tik.login("admin", "") 241 | tik.run_interpreter() 242 | 243 | if __name__ == "__main__": 244 | test() 245 | 246 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from RosAPI import Core 4 | 5 | def prettify(data): 6 | for x in data: 7 | for y in x.keys(): 8 | print "%-20s: %50s" % (y, x[y]) 9 | 10 | if __name__ == "__main__": 11 | a = Core("172.16.1.1") 12 | a.login("admin", "") 13 | #a.run_interpreter() 14 | prettify(a.response_handler(a.talk(["/ip/address/print"]))) 15 | --------------------------------------------------------------------------------