def saveConfig(mtFileName, addr, user="******", password="", logFile=None): child = mtexpect.mtSpawn(addr, user, password, logFile) debugLog("Got prompt, sending 'export' command") child.sendline('export file={}\r\n'.format(mtFileName)) child.sendline('/ip\r\n') child.expect(u'.*admin@.*] /ip> ', timeout=30)
def mtSpawn(addr, user="******", password="", logFile=None): if isMac(addr): debugLog("Starting MAC-telnet") child = pexpect.spawnu('./mactelnet -u "{}+cte" -p "{}" {}'.format( user, password, addr), logfile=logFile) child.expect(u'Connecting to .*') debugLog("Connected to " + addr) else: debugLog("Starting telnet") # Make sure the host is reachable by pinging it first, otherwist # we'll get a strang error from pexpect since the telnet output # will not make much sense netutils.ping(addr) child = pexpect.spawnu("telnet {}".format(addr)) child.expect(u'Login: '******'Password: '******'.*admin@.*] > ', timeout=30) debugLog("Got prompt!") return child
def runMndp(timeout, specificMac=None): # This is a wrapper: the mndp program binds to 5678, which is sometimes in use, # causing a closed connection error. The error is generate immediately, so # it shouldn't affect the timeout too much attempts = 10 success = False foundDevices = None while attempts > 0 and not success: attempts -= 1 try: foundDevices = runMndpInternal(timeout, specificMac) success = True except (TimedIOConnectionClosedException, MNDPJsonParseException): debugLog( "Got a closed connection or parse error, trying again in one second" ) time.sleep(1) if not success: raise TimedIOConnectionClosedException return foundDevices
def saveConfig(mtFileName, addr, user="******", password="", logFile=None): child = mtexpect.mtSpawn(addr, user, password, logFile) debugLog("Got prompt, sending 'system backup' command") child.sendline('system backup\r\n') child.expect(u'.*admin@.*] /system backup> ', timeout=10) debugLog("Got new system prompt, sending save command") child.sendline('save name={}\r\n'.format(mtFileName)) child.sendline('/\r\n') child.expect(u'.*admin@.*] > ', timeout=30)
def enableDHCPClient(macAddr, interface, user="******", password="", logFile=None): child = mtexpect.mtSpawn(macAddr, user, password, logFile) debugLog("Got prompt, sending 'ip dhcp-client' command") child.sendline('ip dhcp-client\r\n') child.expect(u'.*admin@.*] /ip dhcp-client> ', timeout=10) debugLog("Got new system prompt, adding dhcp client line") child.sendline('add interface={} disabled=no\r\n'.format(interface)) child.sendline('/\r\n') child.expect(u'.*admin@.*] > ', timeout=10) time.sleep(1)
def getIPAddress(macAddr, user, passw, mndpTimeout=10.0): debugLog("Getting current settings using MNDP") info = mndp.runMndp(mndpTimeout, macAddr) if not info: raise IPNotFoundException( "Couldn't detect information using MNDP for specified MAC address") if info['ip']: _checkIPReachable(info) return info debugLog("Enabling DHCP client") interface = info['iface'] enableDHCPClient(macAddr, interface, user, passw) # Apparently it's possible that in the new mndp run the macaddress # doesn't appear. This then causes an error since 'info' is None # The check should be done a couple of times maxChecks = 4 while maxChecks > 0: delay = 5 debugLog( "Rechecking IP address using MNDP in {} seconds".format(delay)) time.sleep(delay) info = mndp.runMndp(mndpTimeout, macAddr) if info and info['ip']: _checkIPReachable(info) return info maxChecks -= 1 raise IPNotFoundException("Couldn't get IP address for " + macAddr)
def resetDevice(addr, user="******", password="", logFile=None): child = mtexpect.mtSpawn(addr, user, password, logFile) debugLog("Removing all files on device") child.sendline("file\r\n") child.expect(u'.*admin@.*] /file> ', timeout=10) child.sendline("remove [find]\r\n") child.sendline("/\r\n") child.expect(u'.*admin@.*] > ', timeout=10) debugLog("Got prompt, sending 'system' command") child.sendline('system\r\n') child.expect(u'.*admin@.*] /system> ', timeout=10) debugLog("Got new system prompt, sending reset command") child.sendline('reset-configuration no-defaults=yes skip-backup=yes\r\n') child.expect(u'.*Dangerous! Reset anyway?.*', timeout=10) debugLog("Sending confirmation") child.sendline('y\r\n') time.sleep(2)
def rebootDevice(addr, user = "******", password = "", logFile = None): child = mtexpect.mtSpawn(addr, user, password, logFile) debugLog("Got prompt, sending 'system' command") child.sendline('system\r\n') child.expect(u'.*admin@.*] /system>.*', timeout=10) debugLog("Got new system prompt, sending reboot command") child.sendline('reboot\r\n') child.expect(u'.*Reboot, yes?.*', timeout=10) debugLog("Sending confirmation") child.sendline('y\r\n') time.sleep(2)
def importSettingsNew(fileName, ip, user, passw, useLogCallback=False): prefix = randomstring.getRandomString(16) mtFileName = prefix + ".rsc" with open(fileName, "rb") as f: ftp = ftplib.FTP(ip) ftp.login(user, passw) ftp.storbinary("STOR " + mtFileName, f) del ftp # allow it to be garbage collected, closing the connection time.sleep(1) class LogObject: def __init__(self): self.logString = "" def write(self, data): self.logString += data def flush(self): pass log = LogObject() child = mtexpect.mtSpawn(ip, user, passw) child.logfile_read = log child.sendline("/import {}\r\n".format(mtFileName)) child.sendline("/ip\r\n") child.expect(u'.*admin@.*] /ip> ', timeout=30) retVal = log.logString debugLog("Read from MikroTik process: " + str(repr(retVal))) retVal = retVal.replace('\r\n', '\n') lines = retVal.splitlines() filteredLines = [] recording = False for l in lines: #print("Investigating", l) if not recording: if "/import {}".format(mtFileName) in l and l.startswith( "[admin@"): recording = True else: if l.startswith("[admin@"): break else: filteredLines.append(l) startIdx = 0 while startIdx < len(filteredLines): if len(filteredLines[startIdx]) == 0: startIdx += 1 else: break filteredLines = filteredLines[startIdx:] endIdx = len(filteredLines) while endIdx > 0: if len(filteredLines[endIdx - 1]) == 0: endIdx -= 1 else: break filteredLines = filteredLines[:endIdx] response = "\n".join(filteredLines) #print(repr(response)) if useLogCallback: print("Output:") print(response) if not "successfully" in response: raise MTImportError("Import response: {}".format(repr(response)))
def extractFile(mtFile, fileName, ipAddr, user, passw): ftp = ftplib.FTP(ipAddr) ftp.login(user, passw) with open(fileName, "wb") as f: debugLog("Files before extraction:") for n in ftp.nlst(): debugLog(" " + n) debugLog("Waiting for file to become complete") while True: time.sleep(1) files = ftp.nlst() if not mtFile + ".in_progress" in files: break debugLog("Retrieving file") ftp.retrbinary("RETR {}".format(mtFile), f.write) ftp.delete("{}".format(mtFile)) debugLog("Files after extraction:") for n in ftp.nlst(): debugLog(" " + n)
def runMndpInternal(timeout, specificMac=None): if not "PYMT_NODEFAULTGWCHECK" in os.environ: if not netutils.hasDefaultGateway(): raise MNDPNoDefaultGatewayException( """No default gateway could be detected, this will interfere with the normal operation of the program. To skip the check and proceed anyway, set the PYMT_NODEFAULTGWCHECK environment variable.""") if specificMac: specificMac = specificMac.lower() foundDevices = {} mndp = None try: mndp = subprocess.Popen(["./mactelnet", "-l", "-B"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=open(os.devnull, 'w')) debugLog("Waiting for mndp results") rw = TimedIO(mndp.stdout.fileno(), mndp.stdin.fileno(), defaultReadTimeout=1.0) startTime = time.time() runSecs = timeout first = True while time.time() - startTime < runSecs: try: l = rw.readLine() while l: debugLog(" mndp line: " + l) if not first: l = subst(l, ["'"], ['"']) l2 = "[" + l + "]" try: arr = json.loads(l2) except Exception as e: print("Warning, couldn't parse json") print(l2) raise MNDPJsonParseException( "Error parsing JSON: " + str(e)) mac = adjustMAC(arr[0]) ip = arr[8] if ip == "0.0.0.0": ip = "" obj = { "mac": mac, "id": arr[1], "platform": arr[2], "version": arr[3], "hw": arr[4], "up": arr[5], "softid": arr[6], "iface": arr[7], "ip": ip } foundDevices[mac] = obj if specificMac and specificMac == mac: return obj else: if l.strip( ) != "MAC-Address,Identity,Platform,Version,Hardware,Uptime,Softid,Ifname,IP": raise Exception( "First line of output is not what's expected") first = False l = rw.readLine() except TimedIOTimeoutException: pass finally: if mndp is not None: try: mndp.send_signal(signal.SIGTERM) time.sleep(1) mndp.send_signal(signal.SIGKILL) except Exception as e: #debugLog("Warning, exception while killing process: ", e) pass if specificMac is not None: # If we get here in this case, it means we didn't find it return None foundDevices = [(n, foundDevices[n]) for n in foundDevices] debugLog("Device order before sort:") for n, d in foundDevices: debugLog(" {}".format(n)) foundDevices.sort() debugLog("Device order after sort:") for n, d in foundDevices: debugLog(" {}".format(n)) foundDevices = [e[1] for e in foundDevices] return foundDevices