diff --git a/miranda.py b/miranda.py
index d719639..a9c20e5 100755
--- a/miranda.py
+++ b/miranda.py
@@ -2,64 +2,60 @@
################################
# Interactive UPNP application #
# Craig Heffner #
-# www.sourcesec.com #
# 07/16/2008 #
-#
-# Notes from Issac:
-# http://code.google.com/p/miranda-upnp/
-# Marks this file as GPL3 licensed by the author
-# I have made minor modificatinos to get it to work with the wemo
-#
################################
-try:
- import sys,os
- from socket import *
- from urllib2 import URLError, HTTPError
- from platform import system as thisSystem
- import xml.dom.minidom as minidom
- import IN,urllib,urllib2
- import readline,time
- import pickle
- import struct
- import base64
- import re
- import getopt
-except Exception,e:
- print 'Unmet dependency:',e
- sys.exit(1)
-
-#Most of the cmdCompleter class was originally written by John Kenyan
-#It serves to tab-complete commands inside the program's shell
-class cmdCompleter:
- def __init__(self,commands):
- self.commands = commands
-
- #Traverses the list of available commands
- def traverse(self,tokens,tree):
- retVal = []
-
- #If there are no commands, or no user input, return null
- if tree is None or len(tokens) == 0:
- return []
- #If there is only one word, only auto-complete the primary commands
- elif len(tokens) == 1:
- retVal = [x+' ' for x in tree if x.startswith(tokens[0])]
- #Else auto-complete for the sub-commands
- elif tokens[0] in tree.keys():
- retVal = self.traverse(tokens[1:],tree[tokens[0]])
- return retVal
-
- #Returns a list of possible commands that match the partial command that the user has entered
- def complete(self,text,state):
- try:
- tokens = readline.get_line_buffer().split()
- if not tokens or readline.get_line_buffer()[-1] == ' ':
- tokens.append('')
- results = self.traverse(tokens,self.commands) + [None]
- return results[state]
- except:
- return
+import sys
+import os
+import re
+import platform
+import xml.dom.minidom as minidom
+import IN
+import urllib
+import urllib2
+import readline
+import time
+import pickle
+import struct
+import base64
+import getopt
+import select
+from socket import *
+
+# Most of the CmdCompleter class was originally written by John Kenyan
+# It serves to tab-complete commands inside the program's shell
+class CmdCompleter:
+ def __init__(self, commands):
+ self.commands = commands
+
+ # Traverses the list of available commands
+ def traverse(self, tokens, tree):
+ retVal = []
+
+ # If there are no commands, or no user input, return null
+ if tree is None or len(tokens) == 0:
+ retVal = []
+ # If there is only one word, only auto-complete the primary commands
+ elif len(tokens) == 1:
+ retVal = [x+' ' for x in tree if x.startswith(tokens[0])]
+ # Else auto-complete for the sub-commands
+ elif tokens[0] in tree.keys():
+ retVal = self.traverse(tokens[1:],tree[tokens[0]])
+
+ return retVal
+
+ # Returns a list of possible commands that match the partial command that the user has entered
+ def complete(self, text, state):
+ try:
+ tokens = readline.get_line_buffer().split()
+ if not tokens or readline.get_line_buffer()[-1] == ' ':
+ tokens.append('')
+ results = self.traverse(tokens,self.commands) + [None]
+ return results[state]
+ except Exception, e:
+ print "Failed to complete command: %s" % str(e)
+
+ return
#UPNP class for getting, sending and parsing SSDP/SOAP XML data (among other things...)
class upnp:
@@ -74,21 +70,24 @@ class upnp:
DEFAULT_PORT = 1900
UPNP_VERSION = '1.0'
MAX_RECV = 8192
+ MAX_HOSTS = 0
+ TIMEOUT = 0
HTTP_HEADERS = []
ENUM_HOSTS = {}
VERBOSE = False
UNIQ = False
DEBUG = False
LOG_FILE = False
+ BATCH_FILE = None
IFACE = None
STARS = '****************************************************************'
csock = False
ssock = False
- def __init__(self, ip=False, port=False, iface=None, appCommands=[]):
+ def __init__(self,ip,port,iface,appCommands):
if appCommands:
- self.completer = cmdCompleter(appCommands)
- if self.initSockets(ip, port, iface) == False:
+ self.completer = CmdCompleter(appCommands)
+ if self.initSockets(ip,port,iface) == False:
print 'UPNP class initialization failed!'
print 'Bye!'
sys.exit(1)
@@ -96,7 +95,7 @@ def __init__(self, ip=False, port=False, iface=None, appCommands=[]):
self.soapEnd = re.compile('<\/.*:envelope>')
#Initialize default sockets
- def initSockets(self, ip, port, iface):
+ def initSockets(self,ip,port,iface):
if self.csock:
self.csock.close()
if self.ssock:
@@ -105,12 +104,12 @@ def initSockets(self, ip, port, iface):
if iface != None:
self.IFACE = iface
if not ip:
- ip = self.DEFAULT_IP
- if not port:
- port = self.DEFAULT_PORT
- self.port = port
- self.ip = ip
-
+ ip = self.DEFAULT_IP
+ if not port:
+ port = self.DEFAULT_PORT
+ self.port = port
+ self.ip = ip
+
try:
#This is needed to join a multicast group
self.mreq = struct.pack("4sl",inet_aton(ip),INADDR_ANY)
@@ -118,11 +117,17 @@ def initSockets(self, ip, port, iface):
#Set up client socket
self.csock = socket(AF_INET,SOCK_DGRAM)
self.csock.setsockopt(IPPROTO_IP,IP_MULTICAST_TTL,2)
-
+
#Set up server socket
self.ssock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
self.ssock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
+ # BSD systems also need to set SO_REUSEPORT
+ try:
+ self.ssock.setsockopt(SOL_SOCKET,SO_REUSEPORT,1)
+ except:
+ pass
+
#Only bind to this interface
if self.IFACE != None:
print '\nBinding to interface',self.IFACE,'...\n'
@@ -161,21 +166,36 @@ def send(self,data,socket):
print "SendTo method failed for %s:%d : %s" % (self.ip,self.port,e)
return False
- #Listen for network data
- def listen(self,size,socket):
+ #Receive network data
+ def recv(self,size,socket):
if socket == False:
socket = self.ssock
- try:
- return socket.recv(size)
+ if self.TIMEOUT:
+ socket.setblocking(0)
+ ready = select.select([socket], [], [], self.TIMEOUT)[0]
+ else:
+ socket.setblocking(1)
+ ready = True
+
+ try:
+ if ready:
+ return socket.recv(size)
+ else:
+ return False
except:
return False
#Create new UDP socket on ip, bound to port
- def createNewListener(self,ip=gethostbyname(gethostname()),port=1900):
+ def createNewListener(self,ip,port):
try:
newsock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
newsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
+ # BSD systems also need to set SO_REUSEPORT
+ try:
+ newsock.setsockopt(SOL_SOCKET,SO_REUSEPORT,1)
+ except:
+ pass
newsock.bind((ip,port))
return newsock
except:
@@ -232,7 +252,7 @@ def parseHeader(self,data,header):
lowerDelim = delimiter.lower()
dataArray = data.split("\r\n")
-
+
#Loop through each line of the headers
for line in dataArray:
lowerLine = line.lower()
@@ -262,14 +282,15 @@ def extractSingleTag(self,data,tag):
#Parses SSDP notify and reply packets, and populates the ENUM_HOSTS dict
def parseSSDPInfo(self,data,showUniq,verbose):
hostFound = False
+ foundLocation = False
messageType = False
xmlFile = False
host = False
page = False
upnpType = None
knownHeaders = {
- 'NOTIFY' : 'notification',
- 'HTTP/1.1 200 OK' : 'reply'
+ 'NOTIFY' : 'notification',
+ 'HTTP/1.1 200 OK' : 'reply'
}
#Use the class defaults if these aren't specified
@@ -287,7 +308,7 @@ def parseSSDPInfo(self,data,showUniq,verbose):
#If this is a notification or a reply message...
if messageType != False:
- #Get the host name and location of it's main UPNP XML file
+ #Get the host name and location of its main UPNP XML file
xmlFile = self.parseHeader(data,"LOCATION")
upnpType = self.parseHeader(data,"SERVER")
(host,page) = self.parseURL(xmlFile)
@@ -317,14 +338,14 @@ def parseSSDPInfo(self,data,showUniq,verbose):
#Get the new host's index number and create an entry in ENUM_HOSTS
index = len(self.ENUM_HOSTS)
self.ENUM_HOSTS[index] = {
- 'name' : host,
- 'dataComplete' : False,
- 'proto' : protocol,
- 'xmlFile' : xmlFile,
- 'serverType' : None,
- 'upnpServer' : upnpType,
- 'deviceList' : {}
- }
+ 'name' : host,
+ 'dataComplete' : False,
+ 'proto' : protocol,
+ 'xmlFile' : xmlFile,
+ 'serverType' : None,
+ 'upnpServer' : upnpType,
+ 'deviceList' : {}
+ }
#Be sure to update the command completer so we can tab complete through this host's data structure
self.updateCmdCompleter(self.ENUM_HOSTS)
@@ -333,6 +354,7 @@ def parseSSDPInfo(self,data,showUniq,verbose):
print "SSDP %s message from %s" % (messageType,host)
if xmlFile:
+ foundLocation = True
print "XML file is located at %s" % xmlFile
if upnpType:
@@ -340,20 +362,22 @@ def parseSSDPInfo(self,data,showUniq,verbose):
print self.STARS
print ''
+
+ return True
#Send GET request for a UPNP XML file
- def getXML(self, url):
+ def getXML(self,url):
headers = {
- 'USER-AGENT':'uPNP/'+self.UPNP_VERSION,
- 'CONTENT-TYPE':'text/xml; charset="utf-8"'
- }
+ 'USER-AGENT':'uPNP/'+self.UPNP_VERSION,
+ 'CONTENT-TYPE':'text/xml; charset="utf-8"'
+ }
- try:
+ try:
#Use urllib2 for the request, it's awesome
- req = urllib2.Request(url, None, headers)
- response = urllib2.urlopen(req)
- output = response.read()
+ req = urllib2.Request(url, None, headers)
+ response = urllib2.urlopen(req)
+ output = response.read()
headers = response.info()
return (headers,output)
except Exception, e:
@@ -361,7 +385,7 @@ def getXML(self, url):
return (False,False)
#Send SOAP request
- def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArguments):
+ def sendSOAP(self,hostName,serviceType,controlURL,actionName,actionArguments):
argList = ''
soapResponse = ''
@@ -393,36 +417,41 @@ def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArgument
argList += '<%s>%s%s>' % (arg,val,arg)
#Create the SOAP request
- soapBody = """
-
-
-
- %s
-
-
-
-""" % (actionName, serviceType, argList, actionName)
+ soapBody = '\n'\
+ '\n'\
+ '\n'\
+ '\t\n'\
+ '%s\n'\
+ '\t\n'\
+ '\n'\
+ '' % (actionName,serviceType,argList,actionName)
#Specify the headers to send with the request
headers = {
- 'Content-Type':'text/xml; charset="utf-8"',
- 'SOAPACTION':'"%s#%s"' % (serviceType,actionName),
- 'Content-Length': len(soapBody),
- 'HOST':hostName,
- 'User-Agent': 'CyberGarage-HTTP/1.0',
- }
+ 'Host':hostName,
+ 'Content-Length':len(soapBody),
+ 'Content-Type':'text/xml',
+ 'SOAPAction':'"%s#%s"' % (serviceType,actionName)
+ }
#Generate the final payload
for head,value in headers.iteritems():
soapRequest += '%s: %s\r\n' % (head,value)
soapRequest += '\r\n%s' % soapBody
-
+
#Send data and go into recieve loop
try:
- sock = socket(AF_INET,SOCK_STREAM)
- sock.connect((host,port))
- sock.send(soapRequest)
- while True:
+ sock = socket(AF_INET,SOCK_STREAM)
+ sock.connect((host,port))
+
+ if self.DEBUG:
+ print self.STARS
+ print soapRequest
+ print self.STARS
+ print ''
+
+ sock.send(soapRequest)
+ while True:
data = sock.recv(self.MAX_RECV)
if not data:
break
@@ -430,10 +459,10 @@ def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArgument
soapResponse += data
if self.soapEnd.search(soapResponse.lower()) != None:
break
- sock.close()
-
+ sock.close()
+
(header,body) = soapResponse.split('\r\n\r\n',1)
- if not header.upper().startswith('HTTP/1.1 200'):
+ if not header.upper().startswith('HTTP/1.') and ' 200 ' in header.split('\r\n')[0]:
print 'SOAP request failed with error code:',header.split('\r\n')[0].split(' ',1)[1]
errorMsg = self.extractSingleTag(body,'errorDescription')
if errorMsg:
@@ -441,27 +470,30 @@ def sendSOAP(self, hostName, serviceType, controlURL, actionName, actionArgument
return False
else:
return body
- except Exception, e:
- print 'Caught socket exception:',e
- sock.close()
+ except Exception, e:
+ print 'Caught socket exception:',e
+ sock.close()
+ return False
+ except KeyboardInterrupt:
+ print ""
+ sock.close()
return False
- except KeyboardInterrupt:
- sock.close()
- return False
+
#Display all info for a given host
def showCompleteHostInfo(self,index,fp):
+ na = 'N/A'
serviceKeys = ['controlURL','eventSubURL','serviceId','SCPDURL','fullName']
if fp == False:
fp = sys.stdout
-
+
if index < 0 or index >= len(self.ENUM_HOSTS):
fp.write('Specified host does not exist...\n')
return
try:
hostInfo = self.ENUM_HOSTS[index]
if hostInfo['dataComplete'] == False:
- print "Cannot show all host info because we don't have it all yet. Try running 'host info %d' first...\n" % index
+ print "Cannot show all host info because I don't have it all yet. Try running 'host info %d' first...\n" % index
fp.write('Host name: %s\n' % hostInfo['name'])
fp.write('UPNP XML File: %s\n\n' % hostInfo['xmlFile'])
@@ -478,21 +510,18 @@ def showCompleteHostInfo(self,index,fp):
for argName,argStruct in actionStruct['arguments'].iteritems():
fp.write('\t\t\t\t\t%s \n' % argName)
for key,val in argStruct.iteritems():
- try:
- if key == 'relatedStateVariable':
- fp.write('\t\t\t\t\t\t%s:\n' % val)
- for k,v in serviceStruct['serviceStateVariables'][val].iteritems():
- fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k,v))
- else:
- fp.write('\t\t\t\t\t\t%s: %s\n' % (key,val))
- except:
- pass
-
+ if key == 'relatedStateVariable':
+ fp.write('\t\t\t\t\t\t%s:\n' % val)
+ for k,v in serviceStruct['serviceStateVariables'][val].iteritems():
+ fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k,v))
+ else:
+ fp.write('\t\t\t\t\t\t%s: %s\n' % (key,val))
+
except Exception, e:
print 'Caught exception while showing host info:',e
#Wrapper function...
- def getHostInfo(self, xmlData, xmlHeaders, index):
+ def getHostInfo(self,xmlData,xmlHeaders,index):
if self.ENUM_HOSTS[index]['dataComplete'] == True:
return
@@ -536,13 +565,15 @@ def parseDeviceInfo(self,xmlRoot,index):
for tag in deviceTags:
try:
deviceEntryPointer[tag] = str(device.getElementsByTagName(tag)[0].childNodes[0].data)
- except Exception:
+ except Exception, e:
if self.VERBOSE:
print 'Device',deviceEntryPointer['fullName'],'does not have a',tag
continue
#Get a list of all services for this device listing
self.parseServiceList(device,deviceEntryPointer,index)
+ return
+
#Parse the list of services specified in the XML file
def parseServiceList(self,xmlRoot,device,index):
serviceEntryPointer = False
@@ -590,7 +621,13 @@ def parseServiceInfo(self,service,index):
#Get the full path to the service's XML file
xmlFile = self.ENUM_HOSTS[index]['proto'] + self.ENUM_HOSTS[index]['name']
if not xmlFile.endswith('/') and not service['SCPDURL'].startswith('/'):
- xmlFile += '/'
+ try:
+ xmlServiceFile = self.ENUM_HOSTS[index]['xmlFile']
+ slashIndex = xmlServiceFile.rfind('/')
+ xmlFile = xmlServiceFile[:slashIndex] + '/'
+ except:
+ xmlFile += '/'
+
if self.ENUM_HOSTS[index]['proto'] in service['SCPDURL']:
xmlFile = service['SCPDURL']
else:
@@ -604,7 +641,7 @@ def parseServiceInfo(self,service,index):
return False
try:
- xmlRoot = minidom.parseString(xmlData)
+ xmlRoot = minidom.parseString(xmlData)
#Get a list of actions for this service
try:
@@ -614,7 +651,6 @@ def parseServiceInfo(self,service,index):
return False
actions = actionList.getElementsByTagName(actionTag)
if actions == []:
- print 'Failed to retrieve actions from service actions list for service %s!' % service['fullName']
return False
#Parse all actions in the service's action list
@@ -625,11 +661,11 @@ def parseServiceInfo(self,service,index):
except:
print 'Failed to obtain service action name (%s)!' % service['fullName']
continue
-
+
#Add the action to the ENUM_HOSTS dictonary
service['actions'][actionName] = {}
service['actions'][actionName]['arguments'] = {}
-
+
#Parse all of the action's arguments
try:
argList = action.getElementsByTagName(argumentList)[0]
@@ -643,7 +679,7 @@ def parseServiceInfo(self,service,index):
if self.VERBOSE:
print 'Action',actionName,'has no arguments!'
continue
-
+
#Loop through the action's arguments, appending them to the ENUM_HOSTS dictionary
for argument in arguments:
try:
@@ -661,7 +697,7 @@ def parseServiceInfo(self,service,index):
print 'Failed to find tag %s for argument %s!' % (tag,argName)
continue
- #Parse all of the state variables for this service
+ #Parse all of the state variables for this service
self.parseServiceStateVars(xmlRoot,service)
except Exception, e:
@@ -716,7 +752,7 @@ def parseServiceStateVars(self,xmlRoot,servicePointer):
except:
servicePointer['serviceStateVariables'][varName]['sendEvents'] = na
- servicePointer['serviceStateVariables'][varName][allowedValueList] = []
+ servicePointer['serviceStateVariables'][varName][allowedValueList] = []
#Get a list of allowed values for this variable
try:
@@ -727,7 +763,7 @@ def parseServiceStateVars(self,xmlRoot,servicePointer):
#Add the list of allowed values to the ENUM_HOSTS dictionary
for val in vals:
servicePointer['serviceStateVariables'][varName][allowedValueList].append(str(val.childNodes[0].data))
-
+
#Get allowed value range for this variable
try:
valList = var.getElementsByTagName(allowedValueRange)[0]
@@ -741,7 +777,7 @@ def parseServiceStateVars(self,xmlRoot,servicePointer):
servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(maximum)[0].childNodes[0].data))
except:
pass
- return True
+ return True
#Update the command completer
def updateCmdCompleter(self,struct):
@@ -759,7 +795,7 @@ def updateCmdCompleter(self,struct):
for key,val in struct.iteritems():
structPtr[str(key)] = val
topLevelKeys[str(key)] = None
-
+
#Update the subCommandList
for subcmd in subCommandList:
self.completer.commands[hostCommand][subcmd] = None
@@ -785,21 +821,21 @@ def updateCmdCompleter(self,struct):
for action,actionData in serviceData['actions'].iteritems():
structPtr[host][device][service][action] = None
self.completer.commands[hostCommand][sendCommand] = structPtr
- except Exception:
+ except Exception,e:
print "Error updating command completer structure; some command completion features might not work..."
return
-
+
################## Action Functions ######################
#These functions handle user commands from the shell
#Actively search for UPNP devices
-def msearch(argc, argv, hp, cycles=99999999):
+def msearch(argc,argv,hp):
defaultST = "upnp:rootdevice"
st = "schemas-upnp-org"
- myip = gethostbyname(gethostname())
+ myip = ''
lport = hp.port
if argc >= 3:
@@ -819,12 +855,12 @@ def msearch(argc, argv, hp, cycles=99999999):
"HOST:%s:%d\r\n"\
"ST:%s\r\n" % (hp.ip,hp.port,st)
for header,value in hp.msearchHeaders.iteritems():
- request += header + ':' + value + "\r\n"
- request += "\r\n"
+ request += header + ':' + value + "\r\n"
+ request += "\r\n"
print "Entering discovery mode for '%s', Ctl+C to stop..." % st
print ''
-
+
#Have to create a new socket since replies will be sent directly to our IP, not the multicast IP
server = hp.createNewListener(myip,lport)
if server == False:
@@ -832,28 +868,45 @@ def msearch(argc, argv, hp, cycles=99999999):
return
hp.send(request,server)
+ count = 0
+ start = time.time()
+
while True:
try:
- hp.parseSSDPInfo(hp.listen(1024,server),False,False)
- except Exception:
- print 'Discover mode halted...'
- server.close()
- break
- cycles -= 1
- if cycles == 0:
- print 'Discover mode halted...'
- server.close()
+ if hp.MAX_HOSTS > 0 and count >= hp.MAX_HOSTS:
+ break
+
+ if hp.TIMEOUT > 0 and (time.time() - start) > hp.TIMEOUT:
+ raise Exception("Timeout exceeded")
+
+ if hp.parseSSDPInfo(hp.recv(1024,server),False,False):
+ count += 1
+
+ except Exception, e:
+ print '\nDiscover mode halted...'
break
#Passively listen for UPNP NOTIFY packets
def pcap(argc,argv,hp):
print 'Entering passive mode, Ctl+C to stop...'
print ''
+
+ count = 0
+ start = time.time()
+
while True:
try:
- hp.parseSSDPInfo(hp.listen(1024,False),False,False)
- except Exception:
- print "Passive mode halted..."
+ if hp.MAX_HOSTS > 0 and count >= hp.MAX_HOSTS:
+ break
+
+ if hp.TIMEOUT > 0 and (time.time() - start) > hp.TIMEOUT:
+ raise Exception ("Timeout exceeded")
+
+ if hp.parseSSDPInfo(hp.recv(1024,False),False,False):
+ count += 1
+
+ except Exception, e:
+ print "\nPassive mode halted..."
break
#Manipulate M-SEARCH header values
@@ -888,7 +941,7 @@ def head(argc,argv,hp):
showHelp(argv[0])
#Manipulate application settings
-def seti(argc,argv,hp):
+def set(argc,argv,hp):
if argc >= 2:
action = argv[1]
if action == 'uniq':
@@ -933,14 +986,30 @@ def seti(argc,argv,hp):
else:
print "Using new socket: %s:%d" % (ip,port)
except Exception, e:
- print 'Caught exception setting new socket:',e
+ print 'Caught exception setting new socket:',e
+ return
+ elif action == 'timeout':
+ if argc == 3:
+ try:
+ hp.TIMEOUT = int(argv[2])
+ except Exception, e:
+ print 'Caught exception setting new timeout value:',e
+ return
+ elif action == 'max':
+ if argc == 3:
+ try:
+ hp.MAX_HOSTS = int(argv[2])
+ except Exception, e:
+ print 'Caught exception setting new max host value:', e
return
elif action == 'show':
print 'Multicast IP: ',hp.ip
- print 'Multicast Port: ',hp.port
- print 'Network Interface: ',hp.IFACE
+ print 'Multicast port: ',hp.port
+ print 'Network interface: ',hp.IFACE
+ print 'Receive timeout: ',hp.TIMEOUT
+ print 'Host discovery limit: ',hp.MAX_HOSTS
print 'Number of known hosts: ',len(hp.ENUM_HOSTS)
- print 'UPNP Version: ',hp.UPNP_VERSION
+ print 'UPNP version: ',hp.UPNP_VERSION
print 'Debug mode: ',hp.DEBUG
print 'Verbose mode: ',hp.VERBOSE
print 'Show only unique hosts:',hp.UNIQ
@@ -953,8 +1022,10 @@ def seti(argc,argv,hp):
#Host command. It's kind of big.
def host(argc,argv,hp):
+ hostInfo = None
indexList = []
indexError = "Host index out of range. Try the 'host list' command to get a list of known hosts"
+
if argc >= 2:
action = argv[1]
if action == 'list':
@@ -965,19 +1036,14 @@ def host(argc,argv,hp):
print "\t[%d] %s" % (index,hostInfo['name'])
return
elif action == 'details':
- hostInfo = False
if argc == 3:
try:
index = int(argv[2])
+ hostInfo = hp.ENUM_HOSTS[index]
except Exception, e:
print indexError
return
- if index < 0 or index >= len(hp.ENUM_HOSTS):
- print indexError
- return
- hostInfo = hp.ENUM_HOSTS[index]
-
try:
#If this host data is already complete, just display it
if hostInfo['dataComplete'] == True:
@@ -985,12 +1051,13 @@ def host(argc,argv,hp):
else:
print "Can't show host info because I don't have it. Please run 'host get %d'" % index
except KeyboardInterrupt, e:
+ print ""
pass
return
elif action == 'summary':
if argc == 3:
-
+
try:
index = int(argv[2])
hostInfo = hp.ENUM_HOSTS[index]
@@ -1035,19 +1102,15 @@ def host(argc,argv,hp):
return
elif action == 'get':
- hostInfo = False
if argc == 3:
try:
index = int(argv[2])
+ hostInfo = hp.ENUM_HOSTS[index]
except:
print indexError
return
- if index < 0 or index >= len(hp.ENUM_HOSTS):
- print "Host index out of range. Try the 'host list' command to get a list of known hosts"
- return
- else:
- hostInfo = hp.ENUM_HOSTS[index]
-
+
+ if hostInfo is not None:
#If this host data is already complete, just display it
if hostInfo['dataComplete'] == True:
print 'Data for this host has already been enumerated!'
@@ -1070,6 +1133,7 @@ def host(argc,argv,hp):
hp.updateCmdCompleter(hp.ENUM_HOSTS)
return
except KeyboardInterrupt, e:
+ print ""
return
elif action == 'send':
@@ -1083,13 +1147,13 @@ def host(argc,argv,hp):
else:
try:
index = int(argv[2])
+ hostInfo = hp.ENUM_HOSTS[index]
except:
print indexError
return
deviceName = argv[3]
serviceName = argv[4]
actionName = argv[5]
- hostInfo = hp.ENUM_HOSTS[index]
actionArgs = False
sendArgs = {}
retTags = []
@@ -1122,7 +1186,7 @@ def host(argc,argv,hp):
stateVar = hostInfo['deviceList'][deviceName]['services'][serviceName]['serviceStateVariables'][actionStateVar]
if argVals['direction'].lower() == 'in':
- print "Required argument:"
+ print "Required argument:"
print "\tArgument Name: ",argName
print "\tData Type: ",stateVar['dataType']
if stateVar.has_key('allowedValueList'):
@@ -1140,27 +1204,32 @@ def host(argc,argv,hp):
print 'Stopping send request...'
return
uInput = ''
-
+
if argc > 0:
inArgCounter += 1
for val in argv:
uInput += val + ' '
-
+
uInput = uInput.strip()
if stateVar['dataType'] == 'bin.base64' and uInput:
uInput = base64.encodestring(uInput)
-
+
sendArgs[argName] = (uInput.strip(),stateVar['dataType'])
except KeyboardInterrupt:
+ print ""
return
print ''
else:
retTags.append((argName,stateVar['dataType']))
- #Remove the above inputs from the command history
+ #Remove the above inputs from the command history
while inArgCounter:
- readline.remove_history_item(readline.get_current_history_length()-1)
+ try:
+ readline.remove_history_item(readline.get_current_history_length()-1)
+ except:
+ pass
+
inArgCounter -= 1
#print 'Requesting',controlURL
@@ -1240,14 +1309,14 @@ def save(argc,argv,hp):
return
else:
showHelp(argv[0])
-
- return
+
+ return
#Load data
def load(argc,argv,hp):
if argc == 2 and argv[1] != 'help':
loadFile = argv[1]
-
+
try:
fp = open(loadFile,'r')
hp.ENUM_HOSTS = {}
@@ -1295,7 +1364,7 @@ def help(argc,argv,hp):
def debug(argc,argv,hp):
command = ''
if hp.DEBUG == False:
- print 'Debug is disabled! To enable, try the seti command...'
+ print 'Debug is disabled! To enable, try the set command...'
return
if argc == 1:
showHelp(argv[0])
@@ -1351,7 +1420,7 @@ def showHelp(command):
'\tExits the interactive shell\n\n'\
'Usage:\n'\
'\t%s',
- 'quickView' :
+ 'quickView' :
'Exit this shell'
},
'save' : {
@@ -1373,24 +1442,26 @@ def showHelp(command):
'quickView' :
'Save current host data to file'
},
- 'seti' : {
+ 'set' : {
'longListing' :
'Description:\n'\
'\tAllows you to view and edit application settings.\n\n'\
'Usage:\n'\
- '\t%s | iface | socket >\n'\
+ '\t%s | iface | socket | timeout | max >\n'\
"\t'show' displays the current program settings\n"\
"\t'uniq' toggles the show-only-uniq-hosts setting when discovering UPNP devices\n"\
"\t'debug' toggles debug mode\n"\
"\t'verbose' toggles verbose mode\n"\
"\t'version' changes the UPNP version used\n"\
"\t'iface' changes the network interface in use\n"\
- "\t'socket' re-sets the multicast IP address and port number used for UPNP discovery\n\n"\
+ "\t'socket' re-sets the multicast IP address and port number used for UPNP discovery\n"\
+ "\t'timeout' sets the receive timeout period for the msearch and pcap commands (default: infinite)\n"\
+ "\t'max' sets the maximum number of hosts to locate during msearch and pcap discovery modes\n\n"\
'Example:\n'\
- '\t> seti socket 239.255.255.250:1900\n'\
- '\t> seti uniq\n\n'\
+ '\t> set socket 239.255.255.250:1900\n'\
+ '\t> set uniq\n\n'\
'Notes:\n'\
- "\tIf given no options, 'seti' will display the current application settings",
+ "\tIf given no options, 'set' will display help options",
'quickView' :
'Show/define application settings'
},
@@ -1470,7 +1541,7 @@ def showHelp(command):
'Restore previous host data from file'
},
'log' : {
- 'longListing' :
+ 'longListing' :
'Description:\n'\
'\tLogs user-supplied commands to a log file\n\n'\
'Usage:\n'\
@@ -1480,7 +1551,7 @@ def showHelp(command):
}
}
-
+
try:
print helpInfo[command]['longListing'] % command
except:
@@ -1491,10 +1562,11 @@ def showHelp(command):
def usage():
print '''
Command line usage: %s [OPTIONS]
-
+
-s Load previous host data from struct file
-l Log user-supplied commands to log file
-i Specify the name of the interface to use (Linux only, requires root)
+ -b Process commands from a file
-u Disable show-uniq-hosts-only option
-d Enable debug mode
-v Enable verbose mode
@@ -1505,7 +1577,7 @@ def usage():
#Check command line options
def parseCliOpts(argc,argv,hp):
try:
- opts,args = getopt.getopt(argv[1:],'s:l:i:udvh')
+ opts,args = getopt.getopt(argv[1:],'s:l:i:b:udvh')
except getopt.GetoptError, e:
print 'Usage Error:',e
usage()
@@ -1527,6 +1599,9 @@ def parseCliOpts(argc,argv,hp):
elif opt == '-v':
hp.VERBOSE = toggleVal(hp.VERBOSE)
print 'Verbose mode enabled!'
+ elif opt == '-b':
+ hp.BATCH_FILE = open(arg, 'r')
+ print "Processing commands from '%s'..." % arg
elif opt == '-h':
usage()
elif opt == '-i':
@@ -1537,7 +1612,7 @@ def parseCliOpts(argc,argv,hp):
#Get a list of network interfaces. This only works on unix boxes.
try:
- if thisSystem() != 'Windows':
+ if platform.system() != 'Windows':
fp = open('/proc/net/dev','r')
for line in fp.readlines():
if ':' in line:
@@ -1553,7 +1628,7 @@ def parseCliOpts(argc,argv,hp):
except Exception,e:
print 'Error opening file:',e
print "If you aren't running Linux, this file may not exist!"
-
+
if not found and len(networkInterfaces) > 0:
print "Failed to find interface '%s'; try one of these:\n" % requestedInterface
for iface in networkInterfaces:
@@ -1574,6 +1649,10 @@ def toggleVal(val):
#Prompt for user input
def getUserInput(hp,shellPrompt):
defaultShellPrompt = 'upnp> '
+
+ if hp.BATCH_FILE is not None:
+ return getFileInput(hp)
+
if shellPrompt == False:
shellPrompt = defaultShellPrompt
@@ -1594,6 +1673,23 @@ def getUserInput(hp,shellPrompt):
return (argc,argv)
+#Reads scripted commands from a file
+def getFileInput(hp):
+ data = False
+ line = hp.BATCH_FILE.readline()
+ if line:
+ data = True
+ line = line.strip()
+
+ argv = line.split()
+ argc = len(argv)
+
+ if not data:
+ hp.BATCH_FILE.close()
+ hp.BATCH_FILE = None
+
+ return (argc,argv)
+
#Main
def main(argc,argv):
#Table of valid commands - all primary commands must have an associated function
@@ -1615,7 +1711,7 @@ def main(argc,argv):
'load' : {
'help' : None
},
- 'seti' : {
+ 'set' : {
'uniq' : None,
'socket' : None,
'show' : None,
@@ -1623,6 +1719,8 @@ def main(argc,argv):
'debug' : None,
'version' : None,
'verbose' : None,
+ 'timeout' : None,
+ 'max' : None,
'help' : None
},
'head' : {
@@ -1662,7 +1760,7 @@ def main(argc,argv):
appCommands['load'][file] = None
#Initialize upnp class
- hp = upnp(False, False, None, appCommands);
+ hp = upnp(False,False,None,appCommands);
#Set up tab completion and command history
readline.parse_and_bind("tab: complete")
@@ -1680,7 +1778,10 @@ def main(argc,argv):
#Main loop
while True:
#Drop user into shell
- (argc,argv) = getUserInput(hp,False)
+ if hp.BATCH_FILE is not None:
+ (argc,argv) = getFileInput(hp)
+ else:
+ (argc,argv) = getUserInput(hp,False)
if argc == 0:
continue
action = argv[0]
@@ -1702,7 +1803,7 @@ def main(argc,argv):
try:
funcPtr(argc,argv,hp)
except KeyboardInterrupt:
- print 'Action interrupted by user...'
+ print '\nAction interrupted by user...'
print ''
continue
print 'Invalid command. Valid commands are:'
@@ -1713,6 +1814,11 @@ def main(argc,argv):
if __name__ == "__main__":
try:
+ print ''
+ print 'Miranda v1.3'
+ print 'The interactive UPnP client'
+ print 'Craig Heffner, http://www.devttys0.com'
+ print ''
main(len(sys.argv),sys.argv)
except Exception, e:
print 'Caught main exception:',e
diff --git a/wemo.py b/wemo.py
index 5160646..ec9d768 100644
--- a/wemo.py
+++ b/wemo.py
@@ -1,7 +1,8 @@
from miranda import upnp, msearch
-conn = upnp()
-msearch(0, 0, conn, 2)
+conn = upnp(False, False, None, [])
+conn.TIMEOUT = 10 # wait for 10 seconds
+msearch(0, 0, conn)
SWITCHES = []