PageRenderTime 59ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/pentest/miranda/miranda.py

https://github.com/sullivanmatt/Raspberry-Pwn
Python | 1709 lines | 1520 code | 95 blank | 94 comment | 147 complexity | c7bab21c7b857bfe9c2add81e79671bc MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, MPL-2.0-no-copyleft-exception, GPL-2.0, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/env python
  2. ################################
  3. # Interactive UPNP application #
  4. # Craig Heffner #
  5. # www.sourcesec.com #
  6. # 07/16/2008 #
  7. ################################
  8. try:
  9. import sys,os
  10. from socket import *
  11. from urllib2 import URLError, HTTPError
  12. from platform import system as thisSystem
  13. import xml.dom.minidom as minidom
  14. import IN,urllib,urllib2
  15. import readline,time
  16. import pickle
  17. import struct
  18. import base64
  19. import re
  20. import getopt
  21. except Exception,e:
  22. print 'Unmet dependency:',e
  23. sys.exit(1)
  24. #Most of the cmdCompleter class was originally written by John Kenyan
  25. #It serves to tab-complete commands inside the program's shell
  26. class cmdCompleter:
  27. def __init__(self,commands):
  28. self.commands = commands
  29. #Traverses the list of available commands
  30. def traverse(self,tokens,tree):
  31. retVal = []
  32. #If there are no commands, or no user input, return null
  33. if tree is None or len(tokens) == 0:
  34. return []
  35. #If there is only one word, only auto-complete the primary commands
  36. elif len(tokens) == 1:
  37. retVal = [x+' ' for x in tree if x.startswith(tokens[0])]
  38. #Else auto-complete for the sub-commands
  39. elif tokens[0] in tree.keys():
  40. retVal = self.traverse(tokens[1:],tree[tokens[0]])
  41. return retVal
  42. #Returns a list of possible commands that match the partial command that the user has entered
  43. def complete(self,text,state):
  44. try:
  45. tokens = readline.get_line_buffer().split()
  46. if not tokens or readline.get_line_buffer()[-1] == ' ':
  47. tokens.append('')
  48. results = self.traverse(tokens,self.commands) + [None]
  49. return results[state]
  50. except:
  51. return
  52. #UPNP class for getting, sending and parsing SSDP/SOAP XML data (among other things...)
  53. class upnp:
  54. ip = False
  55. port = False
  56. completer = False
  57. msearchHeaders = {
  58. 'MAN' : '"ssdp:discover"',
  59. 'MX' : '2'
  60. }
  61. DEFAULT_IP = "239.255.255.250"
  62. DEFAULT_PORT = 1900
  63. UPNP_VERSION = '1.0'
  64. MAX_RECV = 8192
  65. HTTP_HEADERS = []
  66. ENUM_HOSTS = {}
  67. VERBOSE = False
  68. UNIQ = False
  69. DEBUG = False
  70. LOG_FILE = False
  71. IFACE = None
  72. STARS = '****************************************************************'
  73. csock = False
  74. ssock = False
  75. def __init__(self,ip,port,iface,appCommands):
  76. if appCommands:
  77. self.completer = cmdCompleter(appCommands)
  78. if self.initSockets(ip,port,iface) == False:
  79. print 'UPNP class initialization failed!'
  80. print 'Bye!'
  81. sys.exit(1)
  82. else:
  83. self.soapEnd = re.compile('<\/.*:envelope>')
  84. #Initialize default sockets
  85. def initSockets(self,ip,port,iface):
  86. if self.csock:
  87. self.csock.close()
  88. if self.ssock:
  89. self.ssock.close()
  90. if iface != None:
  91. self.IFACE = iface
  92. if not ip:
  93. ip = self.DEFAULT_IP
  94. if not port:
  95. port = self.DEFAULT_PORT
  96. self.port = port
  97. self.ip = ip
  98. try:
  99. #This is needed to join a multicast group
  100. self.mreq = struct.pack("4sl",inet_aton(ip),INADDR_ANY)
  101. #Set up client socket
  102. self.csock = socket(AF_INET,SOCK_DGRAM)
  103. self.csock.setsockopt(IPPROTO_IP,IP_MULTICAST_TTL,2)
  104. #Set up server socket
  105. self.ssock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
  106. self.ssock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
  107. #Only bind to this interface
  108. if self.IFACE != None:
  109. print '\nBinding to interface',self.IFACE,'...\n'
  110. self.ssock.setsockopt(SOL_SOCKET,IN.SO_BINDTODEVICE,struct.pack("%ds" % (len(self.IFACE)+1,), self.IFACE))
  111. self.csock.setsockopt(SOL_SOCKET,IN.SO_BINDTODEVICE,struct.pack("%ds" % (len(self.IFACE)+1,), self.IFACE))
  112. try:
  113. self.ssock.bind(('',self.port))
  114. except Exception, e:
  115. print "WARNING: Failed to bind %s:%d: %s" , (self.ip,self.port,e)
  116. try:
  117. self.ssock.setsockopt(IPPROTO_IP,IP_ADD_MEMBERSHIP,self.mreq)
  118. except Exception, e:
  119. print 'WARNING: Failed to join multicast group:',e
  120. except Exception, e:
  121. print "Failed to initialize UPNP sockets:",e
  122. return False
  123. return True
  124. #Clean up file/socket descriptors
  125. def cleanup(self):
  126. if self.LOG_FILE != False:
  127. self.LOG_FILE.close()
  128. self.csock.close()
  129. self.ssock.close()
  130. #Send network data
  131. def send(self,data,socket):
  132. #By default, use the client socket that's part of this class
  133. if socket == False:
  134. socket = self.csock
  135. try:
  136. socket.sendto(data,(self.ip,self.port))
  137. return True
  138. except Exception, e:
  139. print "SendTo method failed for %s:%d : %s" % (self.ip,self.port,e)
  140. return False
  141. #Listen for network data
  142. def listen(self,size,socket):
  143. if socket == False:
  144. socket = self.ssock
  145. try:
  146. return socket.recv(size)
  147. except:
  148. return False
  149. #Create new UDP socket on ip, bound to port
  150. def createNewListener(self,ip,port):
  151. try:
  152. newsock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
  153. newsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
  154. newsock.bind((ip,port))
  155. return newsock
  156. except:
  157. return False
  158. #Return the class's primary server socket
  159. def listener(self):
  160. return self.ssock
  161. #Return the class's primary client socket
  162. def sender(self):
  163. return self.csock
  164. #Parse a URL, return the host and the page
  165. def parseURL(self,url):
  166. delim = '://'
  167. host = False
  168. page = False
  169. #Split the host and page
  170. try:
  171. (host,page) = url.split(delim)[1].split('/',1)
  172. page = '/' + page
  173. except:
  174. #If '://' is not in the url, then it's not a full URL, so assume that it's just a relative path
  175. page = url
  176. return (host,page)
  177. #Pull the name of the device type from a device type string
  178. #The device type string looks like: 'urn:schemas-upnp-org:device:WANDevice:1'
  179. def parseDeviceTypeName(self,string):
  180. delim1 = 'device:'
  181. delim2 = ':'
  182. if delim1 in string and not string.endswith(delim1):
  183. return string.split(delim1)[1].split(delim2,1)[0]
  184. return False
  185. #Pull the name of the service type from a service type string
  186. #The service type string looks like: 'urn:schemas-upnp-org:service:Layer3Forwarding:1'
  187. def parseServiceTypeName(self,string):
  188. delim1 = 'service:'
  189. delim2 = ':'
  190. if delim1 in string and not string.endswith(delim1):
  191. return string.split(delim1)[1].split(delim2,1)[0]
  192. return False
  193. #Pull the header info for the specified HTTP header - case insensitive
  194. def parseHeader(self,data,header):
  195. delimiter = "%s:" % header
  196. defaultRet = False
  197. lowerDelim = delimiter.lower()
  198. dataArray = data.split("\r\n")
  199. #Loop through each line of the headers
  200. for line in dataArray:
  201. lowerLine = line.lower()
  202. #Does this line start with the header we're looking for?
  203. if lowerLine.startswith(lowerDelim):
  204. try:
  205. return line.split(':',1)[1].strip()
  206. except:
  207. print "Failure parsing header data for %s" % header
  208. return defaultRet
  209. #Extract the contents of a single XML tag from the data
  210. def extractSingleTag(self,data,tag):
  211. startTag = "<%s" % tag
  212. endTag = "</%s>" % tag
  213. try:
  214. tmp = data.split(startTag)[1]
  215. index = tmp.find('>')
  216. if index != -1:
  217. index += 1
  218. return tmp[index:].split(endTag)[0].strip()
  219. except:
  220. pass
  221. return None
  222. #Parses SSDP notify and reply packets, and populates the ENUM_HOSTS dict
  223. def parseSSDPInfo(self,data,showUniq,verbose):
  224. hostFound = False
  225. foundLocation = False
  226. messageType = False
  227. xmlFile = False
  228. host = False
  229. page = False
  230. upnpType = None
  231. knownHeaders = {
  232. 'NOTIFY' : 'notification',
  233. 'HTTP/1.1 200 OK' : 'reply'
  234. }
  235. #Use the class defaults if these aren't specified
  236. if showUniq == False:
  237. showUniq = self.UNIQ
  238. if verbose == False:
  239. verbose = self.VERBOSE
  240. #Is the SSDP packet a notification, a reply, or neither?
  241. for text,messageType in knownHeaders.iteritems():
  242. if data.upper().startswith(text):
  243. break
  244. else:
  245. messageType = False
  246. #If this is a notification or a reply message...
  247. if messageType != False:
  248. #Get the host name and location of it's main UPNP XML file
  249. xmlFile = self.parseHeader(data,"LOCATION")
  250. upnpType = self.parseHeader(data,"SERVER")
  251. (host,page) = self.parseURL(xmlFile)
  252. #Sanity check to make sure we got all the info we need
  253. if xmlFile == False or host == False or page == False:
  254. print 'ERROR parsing recieved header:'
  255. print self.STARS
  256. print data
  257. print self.STARS
  258. print ''
  259. return False
  260. #Get the protocol in use (i.e., http, https, etc)
  261. protocol = xmlFile.split('://')[0]+'://'
  262. #Check if we've seen this host before; add to the list of hosts if:
  263. # 1. This is a new host
  264. # 2. We've already seen this host, but the uniq hosts setting is disabled
  265. for hostID,hostInfo in self.ENUM_HOSTS.iteritems():
  266. if hostInfo['name'] == host:
  267. hostFound = True
  268. if self.UNIQ:
  269. return False
  270. if (hostFound and not self.UNIQ) or not hostFound:
  271. #Get the new host's index number and create an entry in ENUM_HOSTS
  272. index = len(self.ENUM_HOSTS)
  273. self.ENUM_HOSTS[index] = {
  274. 'name' : host,
  275. 'dataComplete' : False,
  276. 'proto' : protocol,
  277. 'xmlFile' : xmlFile,
  278. 'serverType' : None,
  279. 'upnpServer' : upnpType,
  280. 'deviceList' : {}
  281. }
  282. #Be sure to update the command completer so we can tab complete through this host's data structure
  283. self.updateCmdCompleter(self.ENUM_HOSTS)
  284. #Print out some basic device info
  285. print self.STARS
  286. print "SSDP %s message from %s" % (messageType,host)
  287. if xmlFile:
  288. foundLocation = True
  289. print "XML file is located at %s" % xmlFile
  290. if upnpType:
  291. print "Device is running %s"% upnpType
  292. print self.STARS
  293. print ''
  294. #Send GET request for a UPNP XML file
  295. def getXML(self,url):
  296. headers = {
  297. 'USER-AGENT':'uPNP/'+self.UPNP_VERSION,
  298. 'CONTENT-TYPE':'text/xml; charset="utf-8"'
  299. }
  300. try:
  301. #Use urllib2 for the request, it's awesome
  302. req = urllib2.Request(url, None, headers)
  303. response = urllib2.urlopen(req)
  304. output = response.read()
  305. headers = response.info()
  306. return (headers,output)
  307. except Exception, e:
  308. print "Request for '%s' failed: %s" % (url,e)
  309. return (False,False)
  310. #Send SOAP request
  311. def sendSOAP(self,hostName,serviceType,controlURL,actionName,actionArguments):
  312. argList = ''
  313. soapResponse = ''
  314. if '://' in controlURL:
  315. urlArray = controlURL.split('/',3)
  316. if len(urlArray) < 4:
  317. controlURL = '/'
  318. else:
  319. controlURL = '/' + urlArray[3]
  320. soapRequest = 'POST %s HTTP/1.1\r\n' % controlURL
  321. #Check if a port number was specified in the host name; default is port 80
  322. if ':' in hostName:
  323. hostNameArray = hostName.split(':')
  324. host = hostNameArray[0]
  325. try:
  326. port = int(hostNameArray[1])
  327. except:
  328. print 'Invalid port specified for host connection:',hostName[1]
  329. return False
  330. else:
  331. host = hostName
  332. port = 80
  333. #Create a string containing all of the SOAP action's arguments and values
  334. for arg,(val,dt) in actionArguments.iteritems():
  335. argList += '<%s>%s</%s>' % (arg,val,arg)
  336. #Create the SOAP request
  337. soapBody = '<?xml version="1.0"?>\n'\
  338. '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n'\
  339. '<SOAP-ENV:Body>\n'\
  340. '\t<m:%s xmlns:m="%s">\n'\
  341. '%s\n'\
  342. '\t</m:%s>\n'\
  343. '</SOAP-ENV:Body>\n'\
  344. '</SOAP-ENV:Envelope>' % (actionName,serviceType,argList,actionName)
  345. #Specify the headers to send with the request
  346. headers = {
  347. 'Host':hostName,
  348. 'Content-Length':len(soapBody),
  349. 'Content-Type':'text/xml',
  350. 'SOAPAction':'"%s#%s"' % (serviceType,actionName)
  351. }
  352. #Generate the final payload
  353. for head,value in headers.iteritems():
  354. soapRequest += '%s: %s\r\n' % (head,value)
  355. soapRequest += '\r\n%s' % soapBody
  356. #Send data and go into recieve loop
  357. try:
  358. sock = socket(AF_INET,SOCK_STREAM)
  359. sock.connect((host,port))
  360. sock.send(soapRequest)
  361. while True:
  362. data = sock.recv(self.MAX_RECV)
  363. if not data:
  364. break
  365. else:
  366. soapResponse += data
  367. if self.soapEnd.search(soapResponse.lower()) != None:
  368. break
  369. sock.close()
  370. (header,body) = soapResponse.split('\r\n\r\n',1)
  371. if not header.upper().startswith('HTTP/1.1 200'):
  372. print 'SOAP request failed with error code:',header.split('\r\n')[0].split(' ',1)[1]
  373. errorMsg = self.extractSingleTag(body,'errorDescription')
  374. if errorMsg:
  375. print 'SOAP error message:',errorMsg
  376. return False
  377. else:
  378. return body
  379. except Exception, e:
  380. print 'Caught socket exception:',e
  381. sock.close()
  382. return False
  383. except KeyboardInterrupt:
  384. sock.close()
  385. return False
  386. #Display all info for a given host
  387. def showCompleteHostInfo(self,index,fp):
  388. na = 'N/A'
  389. serviceKeys = ['controlURL','eventSubURL','serviceId','SCPDURL','fullName']
  390. if fp == False:
  391. fp = sys.stdout
  392. if index < 0 or index >= len(self.ENUM_HOSTS):
  393. fp.write('Specified host does not exist...\n')
  394. return
  395. try:
  396. hostInfo = self.ENUM_HOSTS[index]
  397. if hostInfo['dataComplete'] == False:
  398. print "Cannot show all host info because we don't have it all yet. Try running 'host info %d' first...\n" % index
  399. fp.write('Host name: %s\n' % hostInfo['name'])
  400. fp.write('UPNP XML File: %s\n\n' % hostInfo['xmlFile'])
  401. fp.write('\nDevice information:\n')
  402. for deviceName,deviceStruct in hostInfo['deviceList'].iteritems():
  403. fp.write('\tDevice Name: %s\n' % deviceName)
  404. for serviceName,serviceStruct in deviceStruct['services'].iteritems():
  405. fp.write('\t\tService Name: %s\n' % serviceName)
  406. for key in serviceKeys:
  407. fp.write('\t\t\t%s: %s\n' % (key,serviceStruct[key]))
  408. fp.write('\t\t\tServiceActions:\n')
  409. for actionName,actionStruct in serviceStruct['actions'].iteritems():
  410. fp.write('\t\t\t\t%s\n' % actionName)
  411. for argName,argStruct in actionStruct['arguments'].iteritems():
  412. fp.write('\t\t\t\t\t%s \n' % argName)
  413. for key,val in argStruct.iteritems():
  414. if key == 'relatedStateVariable':
  415. fp.write('\t\t\t\t\t\t%s:\n' % val)
  416. for k,v in serviceStruct['serviceStateVariables'][val].iteritems():
  417. fp.write('\t\t\t\t\t\t\t%s: %s\n' % (k,v))
  418. else:
  419. fp.write('\t\t\t\t\t\t%s: %s\n' % (key,val))
  420. except Exception, e:
  421. print 'Caught exception while showing host info:',e
  422. #Wrapper function...
  423. def getHostInfo(self,xmlData,xmlHeaders,index):
  424. if self.ENUM_HOSTS[index]['dataComplete'] == True:
  425. return
  426. if index >= 0 and index < len(self.ENUM_HOSTS):
  427. try:
  428. xmlRoot = minidom.parseString(xmlData)
  429. self.parseDeviceInfo(xmlRoot,index)
  430. self.ENUM_HOSTS[index]['serverType'] = xmlHeaders.getheader('Server')
  431. self.ENUM_HOSTS[index]['dataComplete'] = True
  432. return True
  433. except Exception, e:
  434. print 'Caught exception while getting host info:',e
  435. return False
  436. #Parse device info from the retrieved XML file
  437. def parseDeviceInfo(self,xmlRoot,index):
  438. deviceEntryPointer = False
  439. devTag = "device"
  440. deviceType = "deviceType"
  441. deviceListEntries = "deviceList"
  442. deviceTags = ["friendlyName","modelDescription","modelName","modelNumber","modelURL","presentationURL","UDN","UPC","manufacturer","manufacturerURL"]
  443. #Find all device entries listed in the XML file
  444. for device in xmlRoot.getElementsByTagName(devTag):
  445. try:
  446. #Get the deviceType string
  447. deviceTypeName = str(device.getElementsByTagName(deviceType)[0].childNodes[0].data)
  448. except:
  449. continue
  450. #Pull out the action device name from the deviceType string
  451. deviceDisplayName = self.parseDeviceTypeName(deviceTypeName)
  452. if not deviceDisplayName:
  453. continue
  454. #Create a new device entry for this host in the ENUM_HOSTS structure
  455. deviceEntryPointer = self.ENUM_HOSTS[index][deviceListEntries][deviceDisplayName] = {}
  456. deviceEntryPointer['fullName'] = deviceTypeName
  457. #Parse out all the device tags for that device
  458. for tag in deviceTags:
  459. try:
  460. deviceEntryPointer[tag] = str(device.getElementsByTagName(tag)[0].childNodes[0].data)
  461. except Exception, e:
  462. if self.VERBOSE:
  463. print 'Device',deviceEntryPointer['fullName'],'does not have a',tag
  464. continue
  465. #Get a list of all services for this device listing
  466. self.parseServiceList(device,deviceEntryPointer,index)
  467. return
  468. #Parse the list of services specified in the XML file
  469. def parseServiceList(self,xmlRoot,device,index):
  470. serviceEntryPointer = False
  471. dictName = "services"
  472. serviceListTag = "serviceList"
  473. serviceTag = "service"
  474. serviceNameTag = "serviceType"
  475. serviceTags = ["serviceId","controlURL","eventSubURL","SCPDURL"]
  476. try:
  477. device[dictName] = {}
  478. #Get a list of all services offered by this device
  479. for service in xmlRoot.getElementsByTagName(serviceListTag)[0].getElementsByTagName(serviceTag):
  480. #Get the full service descriptor
  481. serviceName = str(service.getElementsByTagName(serviceNameTag)[0].childNodes[0].data)
  482. #Get the service name from the service descriptor string
  483. serviceDisplayName = self.parseServiceTypeName(serviceName)
  484. if not serviceDisplayName:
  485. continue
  486. #Create new service entry for the device in ENUM_HOSTS
  487. serviceEntryPointer = device[dictName][serviceDisplayName] = {}
  488. serviceEntryPointer['fullName'] = serviceName
  489. #Get all of the required service info and add it to ENUM_HOSTS
  490. for tag in serviceTags:
  491. serviceEntryPointer[tag] = str(service.getElementsByTagName(tag)[0].childNodes[0].data)
  492. #Get specific service info about this service
  493. self.parseServiceInfo(serviceEntryPointer,index)
  494. except Exception, e:
  495. print 'Caught exception while parsing device service list:',e
  496. #Parse details about each service (arguements, variables, etc)
  497. def parseServiceInfo(self,service,index):
  498. argIndex = 0
  499. argTags = ['direction','relatedStateVariable']
  500. actionList = 'actionList'
  501. actionTag = 'action'
  502. nameTag = 'name'
  503. argumentList = 'argumentList'
  504. argumentTag = 'argument'
  505. #Get the full path to the service's XML file
  506. xmlFile = self.ENUM_HOSTS[index]['proto'] + self.ENUM_HOSTS[index]['name']
  507. if not xmlFile.endswith('/') and not service['SCPDURL'].startswith('/'):
  508. xmlFile += '/'
  509. if self.ENUM_HOSTS[index]['proto'] in service['SCPDURL']:
  510. xmlFile = service['SCPDURL']
  511. else:
  512. xmlFile += service['SCPDURL']
  513. service['actions'] = {}
  514. #Get the XML file that describes this service
  515. (xmlHeaders,xmlData) = self.getXML(xmlFile)
  516. if not xmlData:
  517. print 'Failed to retrieve service descriptor located at:',xmlFile
  518. return False
  519. try:
  520. xmlRoot = minidom.parseString(xmlData)
  521. #Get a list of actions for this service
  522. try:
  523. actionList = xmlRoot.getElementsByTagName(actionList)[0]
  524. except:
  525. print 'Failed to retrieve action list for service %s!' % service['fullName']
  526. return False
  527. actions = actionList.getElementsByTagName(actionTag)
  528. if actions == []:
  529. print 'Failed to retrieve actions from service actions list for service %s!' % service['fullName']
  530. return False
  531. #Parse all actions in the service's action list
  532. for action in actions:
  533. #Get the action's name
  534. try:
  535. actionName = str(action.getElementsByTagName(nameTag)[0].childNodes[0].data).strip()
  536. except:
  537. print 'Failed to obtain service action name (%s)!' % service['fullName']
  538. continue
  539. #Add the action to the ENUM_HOSTS dictonary
  540. service['actions'][actionName] = {}
  541. service['actions'][actionName]['arguments'] = {}
  542. #Parse all of the action's arguments
  543. try:
  544. argList = action.getElementsByTagName(argumentList)[0]
  545. except:
  546. #Some actions may take no arguments, so continue without raising an error here...
  547. continue
  548. #Get all the arguments in this action's argument list
  549. arguments = argList.getElementsByTagName(argumentTag)
  550. if arguments == []:
  551. if self.VERBOSE:
  552. print 'Action',actionName,'has no arguments!'
  553. continue
  554. #Loop through the action's arguments, appending them to the ENUM_HOSTS dictionary
  555. for argument in arguments:
  556. try:
  557. argName = str(argument.getElementsByTagName(nameTag)[0].childNodes[0].data)
  558. except:
  559. print 'Failed to get argument name for',actionName
  560. continue
  561. service['actions'][actionName]['arguments'][argName] = {}
  562. #Get each required argument tag value and add them to ENUM_HOSTS
  563. for tag in argTags:
  564. try:
  565. service['actions'][actionName]['arguments'][argName][tag] = str(argument.getElementsByTagName(tag)[0].childNodes[0].data)
  566. except:
  567. print 'Failed to find tag %s for argument %s!' % (tag,argName)
  568. continue
  569. #Parse all of the state variables for this service
  570. self.parseServiceStateVars(xmlRoot,service)
  571. except Exception, e:
  572. print 'Caught exception while parsing Service info for service %s: %s' % (service['fullName'],str(e))
  573. return False
  574. return True
  575. #Get info about a service's state variables
  576. def parseServiceStateVars(self,xmlRoot,servicePointer):
  577. na = 'N/A'
  578. varVals = ['sendEvents','dataType','defaultValue','allowedValues']
  579. serviceStateTable = 'serviceStateTable'
  580. stateVariable = 'stateVariable'
  581. nameTag = 'name'
  582. dataType = 'dataType'
  583. sendEvents = 'sendEvents'
  584. allowedValueList = 'allowedValueList'
  585. allowedValue = 'allowedValue'
  586. allowedValueRange = 'allowedValueRange'
  587. minimum = 'minimum'
  588. maximum = 'maximum'
  589. #Create the serviceStateVariables entry for this service in ENUM_HOSTS
  590. servicePointer['serviceStateVariables'] = {}
  591. #Get a list of all state variables associated with this service
  592. try:
  593. stateVars = xmlRoot.getElementsByTagName(serviceStateTable)[0].getElementsByTagName(stateVariable)
  594. except:
  595. #Don't necessarily want to throw an error here, as there may be no service state variables
  596. return False
  597. #Loop through all state variables
  598. for var in stateVars:
  599. for tag in varVals:
  600. #Get variable name
  601. try:
  602. varName = str(var.getElementsByTagName(nameTag)[0].childNodes[0].data)
  603. except:
  604. print 'Failed to get service state variable name for service %s!' % servicePointer['fullName']
  605. continue
  606. servicePointer['serviceStateVariables'][varName] = {}
  607. try:
  608. servicePointer['serviceStateVariables'][varName]['dataType'] = str(var.getElementsByTagName(dataType)[0].childNodes[0].data)
  609. except:
  610. servicePointer['serviceStateVariables'][varName]['dataType'] = na
  611. try:
  612. servicePointer['serviceStateVariables'][varName]['sendEvents'] = str(var.getElementsByTagName(sendEvents)[0].childNodes[0].data)
  613. except:
  614. servicePointer['serviceStateVariables'][varName]['sendEvents'] = na
  615. servicePointer['serviceStateVariables'][varName][allowedValueList] = []
  616. #Get a list of allowed values for this variable
  617. try:
  618. vals = var.getElementsByTagName(allowedValueList)[0].getElementsByTagName(allowedValue)
  619. except:
  620. pass
  621. else:
  622. #Add the list of allowed values to the ENUM_HOSTS dictionary
  623. for val in vals:
  624. servicePointer['serviceStateVariables'][varName][allowedValueList].append(str(val.childNodes[0].data))
  625. #Get allowed value range for this variable
  626. try:
  627. valList = var.getElementsByTagName(allowedValueRange)[0]
  628. except:
  629. pass
  630. else:
  631. #Add the max and min values to the ENUM_HOSTS dictionary
  632. servicePointer['serviceStateVariables'][varName][allowedValueRange] = []
  633. try:
  634. servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(minimum)[0].childNodes[0].data))
  635. servicePointer['serviceStateVariables'][varName][allowedValueRange].append(str(valList.getElementsByTagName(maximum)[0].childNodes[0].data))
  636. except:
  637. pass
  638. return True
  639. #Update the command completer
  640. def updateCmdCompleter(self,struct):
  641. indexOnlyList = {
  642. 'host' : ['get','details','summary'],
  643. 'save' : ['info']
  644. }
  645. hostCommand = 'host'
  646. subCommandList = ['info']
  647. sendCommand = 'send'
  648. try:
  649. structPtr = {}
  650. topLevelKeys = {}
  651. for key,val in struct.iteritems():
  652. structPtr[str(key)] = val
  653. topLevelKeys[str(key)] = None
  654. #Update the subCommandList
  655. for subcmd in subCommandList:
  656. self.completer.commands[hostCommand][subcmd] = None
  657. self.completer.commands[hostCommand][subcmd] = structPtr
  658. #Update the indexOnlyList
  659. for cmd,data in indexOnlyList.iteritems():
  660. for subcmd in data:
  661. self.completer.commands[cmd][subcmd] = topLevelKeys
  662. #This is for updating the sendCommand key
  663. structPtr = {}
  664. for hostIndex,hostData in struct.iteritems():
  665. host = str(hostIndex)
  666. structPtr[host] = {}
  667. if hostData.has_key('deviceList'):
  668. for device,deviceData in hostData['deviceList'].iteritems():
  669. structPtr[host][device] = {}
  670. if deviceData.has_key('services'):
  671. for service,serviceData in deviceData['services'].iteritems():
  672. structPtr[host][device][service] = {}
  673. if serviceData.has_key('actions'):
  674. for action,actionData in serviceData['actions'].iteritems():
  675. structPtr[host][device][service][action] = None
  676. self.completer.commands[hostCommand][sendCommand] = structPtr
  677. except Exception,e:
  678. print "Error updating command completer structure; some command completion features might not work..."
  679. return
  680. ################## Action Functions ######################
  681. #These functions handle user commands from the shell
  682. #Actively search for UPNP devices
  683. def msearch(argc,argv,hp):
  684. defaultST = "upnp:rootdevice"
  685. st = "schemas-upnp-org"
  686. myip = ''
  687. lport = hp.port
  688. if argc >= 3:
  689. if argc == 4:
  690. st = argv[1]
  691. searchType = argv[2]
  692. searchName = argv[3]
  693. else:
  694. searchType = argv[1]
  695. searchName = argv[2]
  696. st = "urn:%s:%s:%s:%s" % (st,searchType,searchName,hp.UPNP_VERSION.split('.')[0])
  697. else:
  698. st = defaultST
  699. #Build the request
  700. request = "M-SEARCH * HTTP/1.1\r\n"\
  701. "HOST:%s:%d\r\n"\
  702. "ST:%s\r\n" % (hp.ip,hp.port,st)
  703. for header,value in hp.msearchHeaders.iteritems():
  704. request += header + ':' + value + "\r\n"
  705. request += "\r\n"
  706. print "Entering discovery mode for '%s', Ctl+C to stop..." % st
  707. print ''
  708. #Have to create a new socket since replies will be sent directly to our IP, not the multicast IP
  709. server = hp.createNewListener(myip,lport)
  710. if server == False:
  711. print 'Failed to bind port %d' % lport
  712. return
  713. hp.send(request,server)
  714. while True:
  715. try:
  716. hp.parseSSDPInfo(hp.listen(1024,server),False,False)
  717. except Exception, e:
  718. print 'Discover mode halted...'
  719. break
  720. #Passively listen for UPNP NOTIFY packets
  721. def pcap(argc,argv,hp):
  722. print 'Entering passive mode, Ctl+C to stop...'
  723. print ''
  724. while True:
  725. try:
  726. hp.parseSSDPInfo(hp.listen(1024,False),False,False)
  727. except Exception, e:
  728. print "Passive mode halted..."
  729. break
  730. #Manipulate M-SEARCH header values
  731. def head(argc,argv,hp):
  732. if argc >= 2:
  733. action = argv[1]
  734. #Show current headers
  735. if action == 'show':
  736. for header,value in hp.msearchHeaders.iteritems():
  737. print header,':',value
  738. return
  739. #Delete the specified header
  740. elif action == 'del':
  741. if argc == 3:
  742. header = argv[2]
  743. if hp.msearchHeaders.has_key(header):
  744. del hp.msearchHeaders[header]
  745. print '%s removed from header list' % header
  746. return
  747. else:
  748. print '%s is not in the current header list' % header
  749. return
  750. #Create/set a headers
  751. elif action == 'set':
  752. if argc == 4:
  753. header = argv[2]
  754. value = argv[3]
  755. hp.msearchHeaders[header] = value
  756. print "Added header: '%s:%s" % (header,value)
  757. return
  758. showHelp(argv[0])
  759. #Manipulate application settings
  760. def seti(argc,argv,hp):
  761. if argc >= 2:
  762. action = argv[1]
  763. if action == 'uniq':
  764. hp.UNIQ = toggleVal(hp.UNIQ)
  765. print "Show unique hosts set to: %s" % hp.UNIQ
  766. return
  767. elif action == 'debug':
  768. hp.DEBUG = toggleVal(hp.DEBUG)
  769. print "Debug mode set to: %s" % hp.DEBUG
  770. return
  771. elif action == 'verbose':
  772. hp.VERBOSE = toggleVal(hp.VERBOSE)
  773. print "Verbose mode set to: %s" % hp.VERBOSE
  774. return
  775. elif action == 'version':
  776. if argc == 3:
  777. hp.UPNP_VERSION = argv[2]
  778. print 'UPNP version set to: %s' % hp.UPNP_VERSION
  779. else:
  780. showHelp(argv[0])
  781. return
  782. elif action == 'iface':
  783. if argc == 3:
  784. hp.IFACE = argv[2]
  785. print 'Interface set to %s, re-binding sockets...' % hp.IFACE
  786. if hp.initSockets(hp.ip,hp.port,hp.IFACE):
  787. print 'Interface change successful!'
  788. else:
  789. print 'Failed to bind new interface - are you sure you have root privilages??'
  790. hp.IFACE = None
  791. return
  792. elif action == 'socket':
  793. if argc == 3:
  794. try:
  795. (ip,port) = argv[2].split(':')
  796. port = int(port)
  797. hp.ip = ip
  798. hp.port = port
  799. hp.cleanup()
  800. if hp.initSockets(ip,port,hp.IFACE) == False:
  801. print "Setting new socket %s:%d failed!" % (ip,port)
  802. else:
  803. print "Using new socket: %s:%d" % (ip,port)
  804. except Exception, e:
  805. print 'Caught exception setting new socket:',e
  806. return
  807. elif action == 'show':
  808. print 'Multicast IP: ',hp.ip
  809. print 'Multicast Port: ',hp.port
  810. print 'Network Interface: ',hp.IFACE
  811. print 'Number of known hosts: ',len(hp.ENUM_HOSTS)
  812. print 'UPNP Version: ',hp.UPNP_VERSION
  813. print 'Debug mode: ',hp.DEBUG
  814. print 'Verbose mode: ',hp.VERBOSE
  815. print 'Show only unique hosts:',hp.UNIQ
  816. print 'Using log file: ',hp.LOG_FILE
  817. return
  818. showHelp(argv[0])
  819. return
  820. #Host command. It's kind of big.
  821. def host(argc,argv,hp):
  822. indexList = []
  823. indexError = "Host index out of range. Try the 'host list' command to get a list of known hosts"
  824. if argc >= 2:
  825. action = argv[1]
  826. if action == 'list':
  827. if len(hp.ENUM_HOSTS) == 0:
  828. print "No known hosts - try running the 'msearch' or 'pcap' commands"
  829. return
  830. for index,hostInfo in hp.ENUM_HOSTS.iteritems():
  831. print "\t[%d] %s" % (index,hostInfo['name'])
  832. return
  833. elif action == 'details':
  834. hostInfo = False
  835. if argc == 3:
  836. try:
  837. index = int(argv[2])
  838. except Exception, e:
  839. print indexError
  840. return
  841. if index < 0 or index >= len(hp.ENUM_HOSTS):
  842. print indexError
  843. return
  844. hostInfo = hp.ENUM_HOSTS[index]
  845. try:
  846. #If this host data is already complete, just display it
  847. if hostInfo['dataComplete'] == True:
  848. hp.showCompleteHostInfo(index,False)
  849. else:
  850. print "Can't show host info because I don't have it. Please run 'host get %d'" % index
  851. except KeyboardInterrupt, e:
  852. pass
  853. return
  854. elif action == 'summary':
  855. if argc == 3:
  856. try:
  857. index = int(argv[2])
  858. hostInfo = hp.ENUM_HOSTS[index]
  859. except:
  860. print indexError
  861. return
  862. print 'Host:',hostInfo['name']
  863. print 'XML File:',hostInfo['xmlFile']
  864. for deviceName,deviceData in hostInfo['deviceList'].iteritems():
  865. print deviceName
  866. for k,v in deviceData.iteritems():
  867. try:
  868. v.has_key(False)
  869. except:
  870. print "\t%s: %s" % (k,v)
  871. print ''
  872. return
  873. elif action == 'info':
  874. output = hp.ENUM_HOSTS
  875. dataStructs = []
  876. for arg in argv[2:]:
  877. try:
  878. arg = int(arg)
  879. except:
  880. pass
  881. output = output[arg]
  882. try:
  883. for k,v in output.iteritems():
  884. try:
  885. v.has_key(False)
  886. dataStructs.append(k)
  887. except:
  888. print k,':',v
  889. continue
  890. except:
  891. print output
  892. for struct in dataStructs:
  893. print struct,': {}'
  894. return
  895. elif action == 'get':
  896. hostInfo = False
  897. if argc == 3:
  898. try:
  899. index = int(argv[2])
  900. except:
  901. print indexError
  902. return
  903. if index < 0 or index >= len(hp.ENUM_HOSTS):
  904. print "Host index out of range. Try the 'host list' command to get a list of known hosts"
  905. return
  906. else:
  907. hostInfo = hp.ENUM_HOSTS[index]
  908. #If this host data is already complete, just display it
  909. if hostInfo['dataComplete'] == True:
  910. print 'Data for this host has already been enumerated!'
  911. return
  912. try:
  913. #Get extended device and service information
  914. if hostInfo != False:
  915. print "Requesting device and service info for %s (this could take a few seconds)..." % hostInfo['name']
  916. print ''
  917. if hostInfo['dataComplete'] == False:
  918. (xmlHeaders,xmlData) = hp.getXML(hostInfo['xmlFile'])
  919. if xmlData == False:
  920. print 'Failed to request host XML file:',hostInfo['xmlFile']
  921. return
  922. if hp.getHostInfo(xmlData,xmlHeaders,index) == False:
  923. print "Failed to get device/service info for %s..." % hostInfo['name']
  924. return
  925. print 'Host data enumeration complete!'
  926. hp.updateCmdCompleter(hp.ENUM_HOSTS)
  927. return
  928. except KeyboardInterrupt, e:
  929. return
  930. elif action == 'send':
  931. #Send SOAP requests
  932. index = False
  933. inArgCounter = 0
  934. if argc != 6:
  935. showHelp(argv[0])
  936. return
  937. else:
  938. try:
  939. index = int(argv[2])
  940. except:
  941. print indexError
  942. return
  943. deviceName = argv[3]
  944. serviceName = argv[4]
  945. actionName = argv[5]
  946. hostInfo = hp.ENUM_HOSTS[index]
  947. actionArgs = False
  948. sendArgs = {}
  949. retTags = []
  950. controlURL = False
  951. fullServiceName = False
  952. #Get the service control URL and full service name
  953. try:
  954. controlURL = hostInfo['proto'] + hostInfo['name']
  955. controlURL2 = hostInfo['deviceList'][deviceName]['services'][serviceName]['controlURL']
  956. if not controlURL.endswith('/') and not controlURL2.startswith('/'):
  957. controlURL += '/'
  958. controlURL += controlURL2
  959. except Exception,e:
  960. print 'Caught exception:',e
  961. print "Are you sure you've run 'host get %d' and specified the correct service name?" % index
  962. return False
  963. #Get action info
  964. try:
  965. actionArgs = hostInfo['deviceList'][deviceName]['services'][serviceName]['actions'][actionName]['arguments']
  966. fullServiceName = hostInfo['deviceList'][deviceName]['services'][serviceName]['fullName']
  967. except Exception,e:
  968. print 'Caught exception:',e
  969. print "Are you sure you've specified the correct action?"
  970. return False
  971. for argName,argVals in actionArgs.iteritems():
  972. actionStateVar = argVals['relatedStateVariable']
  973. stateVar = hostInfo['deviceList'][deviceName]['services'][serviceName]['serviceStateVariables'][actionStateVar]
  974. if argVals['direction'].lower() == 'in':
  975. print "Required argument:"
  976. print "\tArgument Name: ",argName
  977. print "\tData Type: ",stateVar['dataType']
  978. if stateVar.has_key('allowedValueList'):
  979. print "\tAllowed Values:",stateVar['allowedValueList']
  980. if stateVar.has_key('allowedValueRange'):
  981. print "\tValue Min: ",stateVar['allowedValueRange'][0]
  982. print "\tValue Max: ",stateVar['allowedValueRange'][1]
  983. if stateVar.has_key('defaultValue'):
  984. print "\tDefault Value: ",stateVar['defaultValue']
  985. prompt = "\tSet %s value to: " % argName
  986. try:
  987. #Get user input for the argument value
  988. (argc,argv) = getUserInput(hp,prompt)
  989. if argv == None:
  990. print 'Stopping send request...'
  991. return
  992. uInput = ''
  993. if argc > 0:
  994. inArgCounter += 1
  995. for val in argv:
  996. uInput += val + ' '
  997. uInput = uInput.strip()
  998. if stateVar['dataType'] == 'bin.base64' and uInput:
  999. uInput = base64.encodestring(uInput)
  1000. sendArgs[argName] = (uInput.strip(),stateVar['dataType'])
  1001. except KeyboardInterrupt:
  1002. return
  1003. print ''
  1004. else:
  1005. retTags.append((argName,stateVar['dataType']))
  1006. #Remove the above inputs from the command history
  1007. while inArgCounter:
  1008. readline.remove_history_item(readline.get_current_history_length()-1)
  1009. inArgCounter -= 1
  1010. #print 'Requesting',controlURL
  1011. soapResponse = hp.sendSOAP(hostInfo['name'],fullServiceName,controlURL,actionName,sendArgs)
  1012. if soapResponse != False:
  1013. #It's easier to just parse this ourselves...
  1014. for (tag,dataType) in retTags:
  1015. tagValue = hp.extractSingleTag(soapResponse,tag)
  1016. if dataType == 'bin.base64' and tagValue != None:
  1017. tagValue = base64.decodestring(tagValue)
  1018. print tag,':',tagValue
  1019. return
  1020. showHelp(argv[0])
  1021. return
  1022. #Save data
  1023. def save(argc,argv,hp):
  1024. suffix = '%s_%s.mir'
  1025. uniqName = ''
  1026. saveType = ''
  1027. fnameIndex = 3
  1028. if argc >= 2:
  1029. if argv[1] == 'help':
  1030. showHelp(argv[0])
  1031. return
  1032. elif argv[1] == 'data':
  1033. saveType = 'struct'
  1034. if argc == 3:
  1035. index = argv[2]
  1036. else:
  1037. index = 'data'
  1038. elif argv[1] == 'info':
  1039. saveType = 'info'
  1040. fnameIndex = 4
  1041. if argc >= 3:
  1042. try:
  1043. index = int(argv[2])
  1044. except Exception, e:
  1045. print 'Host index is not a number!'
  1046. showHelp(argv[0])
  1047. return
  1048. else:
  1049. showHelp(argv[0])
  1050. return
  1051. if argc == fnameIndex:
  1052. uniqName = argv[fnameIndex-1]
  1053. else:
  1054. uniqName = index
  1055. else:
  1056. showHelp(argv[0])
  1057. return
  1058. fileName = suffix % (saveType,uniqName)
  1059. if os.path.exists(fileName):
  1060. print "File '%s' already exists! Please try again..." % fileName
  1061. return
  1062. if saveType == 'struct':
  1063. try:
  1064. fp = open(fileName,'w')
  1065. pickle.dump(hp.ENUM_HOSTS,fp)
  1066. fp.close()
  1067. print "Host data saved to '%s'" % fileName
  1068. except Exception, e:
  1069. print 'Caught exception saving host data:',e
  1070. elif saveType == 'info':
  1071. try:
  1072. fp = open(fileName,'w')
  1073. hp.showCompleteHostInfo(index,fp)
  1074. fp.close()
  1075. print "Host info for '%s' saved to '%s'" % (hp.ENUM_HOSTS[index]['name'],fileName)
  1076. except Exception, e:
  1077. print 'Failed to save host info:',e
  1078. return
  1079. else:
  1080. showHelp(argv[0])
  1081. return
  1082. #Load data
  1083. def load(argc,argv,hp):
  1084. if argc == 2 and argv[1] != 'help':
  1085. loadFile = argv[1]
  1086. try:
  1087. fp = open(loadFile,'r')
  1088. hp.ENUM_HOSTS = {}
  1089. hp.ENUM_HOSTS = pickle.load(fp)
  1090. fp.close()
  1091. hp.updateCmdCompleter(hp.ENUM_HOSTS)
  1092. print 'Host data restored:'
  1093. print ''
  1094. host(2,['host','list'],hp)
  1095. return
  1096. except Exception, e:
  1097. print 'Caught exception while restoring host data:',e
  1098. showHelp(argv[0])
  1099. #Open log file
  1100. def log(argc,argv,hp):
  1101. if argc == 2:
  1102. logFile = argv[1]
  1103. try:
  1104. fp = open(logFile,'a')
  1105. except Exception, e:
  1106. print 'Failed to open %s for logging: %s' % (logFile,e)
  1107. return
  1108. try:
  1109. hp.LOG_FILE = fp
  1110. ts = []
  1111. for x in time.localtime():
  1112. ts.append(x)
  1113. theTime = "%d-%d-%d, %d:%d:%d" % (ts[0],ts[1],ts[2],ts[3],ts[4],ts[5])
  1114. hp.LOG_FILE.write("\n### Logging started at: %s ###\n" % theTime)
  1115. except Exception, e:
  1116. print "Cannot write to file '%s': %s" % (logFile,e)
  1117. hp.LOG_FILE = False
  1118. return
  1119. print "Commands will be logged to: '%s'" % logFile
  1120. return
  1121. showHelp(argv[0])
  1122. #Show help
  1123. def help(argc,argv,hp):
  1124. showHelp(False)
  1125. #Debug, disabled by default
  1126. def debug(argc,argv,hp):
  1127. command = ''
  1128. if hp.DEBUG == False:
  1129. print 'Debug is disabled! To enable, try the seti command...'
  1130. return
  1131. if argc == 1:
  1132. showHelp(argv[0])
  1133. else:
  1134. for cmd in argv[1:]:
  1135. command += cmd + ' '
  1136. command = command.strip()
  1137. print eval(command)
  1138. return
  1139. #Quit!
  1140. def exit(argc,argv,hp):
  1141. quit(argc,argv,hp)
  1142. #Quit!
  1143. def quit(argc,argv,hp):
  1144. if argc == 2 and argv[1] == 'help':
  1145. showHelp(argv[0])
  1146. return
  1147. print 'Bye!'
  1148. print ''
  1149. hp.cleanup()
  1150. sys.exit(0)
  1151. ################ End Action Functions ######################
  1152. #Show command help
  1153. def showHelp(command):
  1154. #Detailed help info for each command
  1155. helpInfo = {
  1156. 'help' : {
  1157. 'longListing':
  1158. 'Description:\n'\
  1159. '\tLists available commands and command descriptions\n\n'\
  1160. 'Usage:\n'\
  1161. '\t%s\n'\
  1162. '\t<command> help',
  1163. 'quickView':
  1164. 'Show program help'
  1165. },
  1166. 'quit' : {
  1167. 'longListing' :
  1168. 'Description:\n'\
  1169. '\tQuits the interactive shell\n\n'\
  1170. 'Usage:\n'\
  1171. '\t%s',
  1172. 'quickView' :
  1173. 'Exit this shell'
  1174. },
  1175. 'exit' : {
  1176. 'longListing' :
  1177. 'Description:\n'\
  1178. '\tExits the interactive shell\n\n'\
  1179. 'Usage:\n'\
  1180. '\t%s',
  1181. 'quickView' :
  1182. 'Exit this shell'
  1183. },
  1184. 'save' : {
  1185. 'longListing' :
  1186. 'Description:\n'\
  1187. '\tSaves current host information to disk.\n\n'\
  1188. 'Usage:\n'\
  1189. '\t%s <data | info <host#>> [file prefix]\n'\
  1190. "\tSpecifying 'data' will save the raw host data to a file suitable for importing later via 'load'\n"\
  1191. "\tSpecifying 'info' will save data for the specified host in a human-readable format\n"\
  1192. "\tSpecifying a file prefix will save files in for format of 'struct_[prefix].mir' and info_[prefix].mir\n\n"\
  1193. 'Example:\n'\
  1194. '\t> save data wrt54g\n'\
  1195. '\t> save info 0 wrt54g\n\n'\
  1196. 'Notes:\n'\
  1197. "\to Data files are saved as 'struct_[prefix].mir'; info files are saved as 'info_[prefix].mir.'\n"\
  1198. "\to If no prefix is specified, the host index number will be used for the prefix.\n"\
  1199. "\to The data saved by the 'save info' command is the same as the output of the 'host details' command.",
  1200. 'quickView' :
  1201. 'Save current host data to file'
  1202. },
  1203. 'seti' : {
  1204. 'longListing' :
  1205. 'Description:\n'\
  1206. '\tAllows you to view and edit application settings.\n\n'\
  1207. 'Usage:\n'\
  1208. '\t%s <show | uniq | debug | verbose | version <version #> | iface <interface> | socket <ip:port> >\n'\
  1209. "\t'show' displays the current program settings\n"\
  1210. "\t'uniq' toggles the show-only-uniq-hosts setting when discovering UPNP devices\n"\
  1211. "\t'debug' toggles debug mode\n"\
  1212. "\t'verbose' toggles verbose mode\n"\
  1213. "\t'version' changes the UPNP version used\n"\
  1214. "\t'iface' changes the network interface in use\n"\
  1215. "\t'socket' re-sets the multicast IP address and port number used for UPNP discovery\n\n"\
  1216. 'Example:\n'\
  1217. '\t> seti socket 239.255.255.250:1900\n'\
  1218. '\t> seti uniq\n\n'\
  1219. 'Notes:\n'\
  1220. "\tIf given no options, 'seti' will display the current application settings",
  1221. 'quickView' :
  1222. 'Show/define application settings'
  1223. },
  1224. 'head' : {
  1225. 'longListing' :
  1226. 'Description:\n'\
  1227. '\tAllows you to view, set, add and delete the SSDP header values used in SSDP transactions\n\n'\
  1228. 'Usage:\n'\
  1229. '\t%s <show | del <header> | set <header> <value>>\n'\
  1230. "\t'set' allows you to set SSDP headers used when sending M-SEARCH queries with the 'msearch' command\n"\
  1231. "\t'del' deletes a current header from the list\n"\
  1232. "\t'show' displays all current header info\n\n"\
  1233. 'Example:\n'\
  1234. '\t> head show\n'\
  1235. '\t> head set MX 3',
  1236. 'quickView' :
  1237. 'Show/define SSDP headers'
  1238. },
  1239. 'host' : {
  1240. 'longListing' :
  1241. 'Description:\n'\
  1242. "\tAllows you to query host information and iteract with a host's actions/services.\n\n"\
  1243. 'Usage:\n'\
  1244. '\t%s <list | get | info | summary | details | send> [host index #]\n'\
  1245. "\t'list' displays an index of all known UPNP hosts along with their respective index numbers\n"\
  1246. "\t'get' gets detailed information about the specified host\n"\
  1247. "\t'details' gets and displays detailed information about the specified host\n"\
  1248. "\t'summary' displays a short summary describing the specified host\n"\
  1249. "\t'info' allows you to enumerate all elements of the hosts object\n"\
  1250. "\t'send' allows you to send SOAP requests to devices and services *\n\n"\
  1251. 'Example:\n'\
  1252. '\t> host list\n'\
  1253. '\t> host get 0\n'\
  1254. '\t> host summary 0\n'\
  1255. '\t> host info 0 deviceList\n'\
  1256. '\t> host send 0 <device name> <service name> <action name>\n\n'\
  1257. 'Notes:\n'\
  1258. "\to All host commands support full tab completion of enumerated arguments\n"\
  1259. "\to All host commands EXCEPT for the 'host send', 'host info' and 'host list' commands take only one argument: the host index number.\n"\
  1260. "\to The host index number can be obtained by running 'host list', which takes no futher arguments.\n"\
  1261. "\to The 'host send' command requires that you also specify the host's device name, service name, and action name that you wish to send,\n\t in that order (see the last example in the Example section of this output). This information can be obtained by viewing the\n\t 'host details' listing, or by querying the host information via the 'host info' command.\n"\
  1262. "\to The 'host info' command allows you to selectively enumerate the host information data structure. All data elements and their\n\t corresponding values are displayed; a value of '{}' indicates that the element is a sub-structure that can be further enumerated\n\t (see the 'host info' example in the Example section of this output).",
  1263. 'quickView' :
  1264. 'View and send host list and host information'
  1265. },
  1266. 'pcap' : {
  1267. 'longListing' :
  1268. 'Description:\n'\
  1269. '\tPassively listens for SSDP NOTIFY messages from UPNP devices\n\n'\
  1270. 'Usage:\n'\
  1271. '\t%s',
  1272. 'quickView' :
  1273. 'Passively listen for UPNP hosts'
  1274. },
  1275. 'msearch' : {
  1276. 'longListing' :
  1277. 'Description:\n'\
  1278. '\tActively searches for UPNP hosts using M-SEARCH queries\n\n'\
  1279. 'Usage:\n'\
  1280. "\t%s [device | service] [<device name> | <service name>]\n"\
  1281. "\tIf no arguments are specified, 'msearch' searches for upnp:rootdevices\n"\
  1282. "\tSpecific device/services types can be searched for using the 'device' or 'service' arguments\n\n"\
  1283. 'Example:\n'\
  1284. '\t> msearch\n'\
  1285. '\t> msearch service WANIPConnection\n'\
  1286. '\t> msearch device InternetGatewayDevice',
  1287. 'quickView' :
  1288. 'Actively locate UPNP hosts'
  1289. },
  1290. 'load' : {
  1291. 'longListing' :
  1292. 'Description:\n'\
  1293. "\tLoads host data from a struct file previously saved with the 'save data' command\n\n"\
  1294. 'Usage:\n'\
  1295. '\t%s <file name>',
  1296. 'quickView' :
  1297. 'Restore previous host data from file'
  1298. },
  1299. 'log' : {
  1300. 'longListing' :
  1301. 'Description:\n'\
  1302. '\tLogs user-supplied commands to a log file\n\n'\
  1303. 'Usage:\n'\
  1304. '\t%s <log file name>',
  1305. 'quickView' :
  1306. 'Logs user-supplied commands to a log file'
  1307. }
  1308. }
  1309. try:
  1310. print helpInfo[command]['longListing'] % command
  1311. except:
  1312. for command,cmdHelp in helpInfo.iteritems():
  1313. print "%s\t\t%s" % (command,cmdHelp['quickView'])
  1314. #Display usage
  1315. def usage():
  1316. print '''
  1317. Command line usage: %s [OPTIONS]
  1318. -s <struct file> Load previous host data from struct file
  1319. -l <log file> Log user-supplied commands to log file
  1320. -i <interface> Specify the name of the interface to use (Linux only, requires root)
  1321. -u Disable show-uniq-hosts-only option
  1322. -d Enable debug mode
  1323. -v Enable verbose mode
  1324. -h Show help
  1325. ''' % sys.argv[0]
  1326. sys.exit(1)
  1327. #Check command line options
  1328. def parseCliOpts(argc,argv,hp):
  1329. try:
  1330. opts,args = getopt.getopt(argv[1:],'s:l:i:udvh')
  1331. except getopt.GetoptError, e:
  1332. print 'Usage Error:',e
  1333. usage()
  1334. else:
  1335. for (opt,arg) in opts:
  1336. if opt == '-s':
  1337. print ''
  1338. load(2,['load',arg],hp)
  1339. print ''
  1340. elif opt == '-l':
  1341. print ''
  1342. log(2,['log',arg],hp)
  1343. print ''
  1344. elif opt == '-u':
  1345. hp.UNIQ = toggleVal(hp.UNIQ)
  1346. elif opt == '-d':
  1347. hp.DEBUG = toggleVal(hp.DEBUG)
  1348. print 'Debug mode enabled!'
  1349. elif opt == '-v':
  1350. hp.VERBOSE = toggleVal(hp.VERBOSE)
  1351. print 'Verbose mode enabled!'
  1352. elif opt == '-h':
  1353. usage()
  1354. elif opt == '-i':
  1355. networkInterfaces = []
  1356. requestedInterface = arg
  1357. interfaceName = None
  1358. found = False
  1359. #Get a list of network interfaces. This only works on unix boxes.
  1360. try:
  1361. if thisSystem() != 'Windows':
  1362. fp = open('/proc/net/dev','r')
  1363. for line in fp.readlines():
  1364. if ':' in line:
  1365. interfaceName = line.split(':')[0].strip()
  1366. if interfaceName == requestedInterface:
  1367. found = True
  1368. break
  1369. else:
  1370. networkInterfaces.append(line.split(':')[0].strip())
  1371. fp.close()
  1372. else:
  1373. networkInterfaces.append('Run ipconfig to get a list of available network interfaces!')
  1374. except Exception,e:
  1375. print 'Error opening file:',e
  1376. print "If you aren't running Linux, this file may not exist!"
  1377. if not found and len(networkInterfaces) > 0:
  1378. print "Failed to find interface '%s'; try one of these:\n" % requestedInterface
  1379. for iface in networkInterfaces:
  1380. print iface
  1381. print ''
  1382. sys.exit(1)
  1383. else:
  1384. if not hp.initSockets(False,False,interfaceName):
  1385. print 'Binding to interface %s failed; are you sure you have root privilages??' % interfaceName
  1386. #Toggle boolean values
  1387. def toggleVal(val):
  1388. if val:
  1389. return False
  1390. else:
  1391. return True
  1392. #Prompt for user input
  1393. def getUserInput(hp,shellPrompt):
  1394. defaultShellPrompt = 'upnp> '
  1395. if shellPrompt == False:
  1396. shellPrompt = defaultShellPrompt
  1397. try:
  1398. uInput = raw_input(shellPrompt).strip()
  1399. argv = uInput.split()
  1400. argc = len(argv)
  1401. except KeyboardInterrupt, e:
  1402. print '\n'
  1403. if shellPrompt == defaultShellPrompt:
  1404. quit(0,[],hp)
  1405. return (0,None)
  1406. if hp.LOG_FILE != False:
  1407. try:
  1408. hp.LOG_FILE.write("%s\n" % uInput)
  1409. except:
  1410. print 'Failed to log data to log file!'
  1411. return (argc,argv)
  1412. #Main
  1413. def main(argc,argv):
  1414. #Table of valid commands - all primary commands must have an associated function
  1415. appCommands = {
  1416. 'help' : {
  1417. 'help' : None
  1418. },
  1419. 'quit' : {
  1420. 'help' : None
  1421. },
  1422. 'exit' : {
  1423. 'help' : None
  1424. },
  1425. 'save' : {
  1426. 'data' : None,
  1427. 'info' : None,
  1428. 'help' : None
  1429. },
  1430. 'load' : {
  1431. 'help' : None
  1432. },
  1433. 'seti' : {
  1434. 'uniq' : None,
  1435. 'socket' : None,
  1436. 'show' : None,
  1437. 'iface' : None,
  1438. 'debug' : None,
  1439. 'version' : None,
  1440. 'verbose' : None,
  1441. 'help' : None
  1442. },
  1443. 'head' : {
  1444. 'set' : None,
  1445. 'show' : None,
  1446. 'del' : None,
  1447. 'help': None
  1448. },
  1449. 'host' : {
  1450. 'list' : None,
  1451. 'info' : None,
  1452. 'get' : None,
  1453. 'details' : None,
  1454. 'send' : None,
  1455. 'summary' : None,
  1456. 'help' : None
  1457. },
  1458. 'pcap' : {
  1459. 'help' : None
  1460. },
  1461. 'msearch' : {
  1462. 'deviā€¦

Large files files are truncated, but you can click here to view the full file