Originally Posted By: FaiNT
1st off, once the connection has been made. u dont need the port mapped. so doing it on /debug, and waiting 30 secs won't break the send.

Thanks. I finished the script yesterday (to some extent) and instead of waiting 30 seconds, I "unmap" the port immediately after the connection is established.

Here is the script that works for basic needs with my NETGEAR router:

Code:
;UPnP Script v1.0

alias upnp.debugpermanent {
  if ($isid) return $false
}
on *:start: {
  upnp.start
  if ($upnp.debugpermanent) .debug -i upnp.dcc upnp.dccdebug
}
on *:unload: {
  unset %upnp.*
}
on *:exit: {
  unset %upnp.*
}
on ^*:logon:*: {
  if ($readini($mircini,ident,active) == yes) upnp.addPortMapping $readini($mircini,ident,port)
  set %upnp.IdentdMap $true
}
on *:error:*: {
  if ($readini($mircini,ident,active) == yes) && (%upnp.IdentdMap) upnp.delPortMapping $readini($mircini,ident,port)
}
on *:connect: {
  if ($readini($mircini,ident,active) == yes) upnp.delPortMapping $readini($mircini,ident,port)
  unset %upnp.IdentdMap
}
alias debug {
  if ((%upnp.debuguse) || ($upnp.debugpermanent)) && ($gettok($debug,-1,92) == upnp.dcc) {
    linesep $iif($active == Status Window,-s,$active)
    echo -a -UPNP- Debug is in use.
    linesep $iif($active == Status Window,-s,$active)
  }
  else {
    debug $1-
    if ($0 > 0) set %upnp.debugparams $$1-
  }
}
alias upnp.dccgetnum {
  if (!$isid) return
  var %i = 0
  if ($$1 == send) {
    while (%i <= $send(0)) {
      if ($send(%i).wid == $$2) return %i
      inc %i
    }
  }
  else {
    while (%i <= $chat(0)) {
      if ($chat(%i).wid == $$2) return %i
      inc %i
    }
  }
  return 0
}
alias upnp.dcccheck {
  var %upnp.dccnum = $upnp.dccgetnum($1,$$2)
  var %upnp.dccstatus = $iif($1 == send,$send(%upnp.dccnum).status,$chat(%upnp.dccnum).status)
  if ($istok(failed:active:sent:inactive,%upnp.dccstatus,58)) || (%upnp.dccnum == 0) {
    .timerupnp.dcc $+ $$3 off
    upnp.delPortMapping $3
  }
}
alias upnp.dccdebug {
  tokenize 32 $1-
  if ($1 != ->) return
  if ($istok($+(:,$chr(1),DCC SEND,/,:,$chr(1),DCC CHAT),$5-6,47)) {
    var %upnp.debugport = $remove($9,$chr(1))
    upnp.addPortMapping %upnp.debugport
    .timerupnp.dcc $+ %upnp.debugport 0 0 upnp.dcccheck $6 $iif($6 == send,$send($send($4,0)).wid,$chat($chat($4,0)).wid) %upnp.debugport
    if (!$upnp.debugpermanent) {
      unset %upnp.debuguse
      .debug off
      if (%upnp.debugbefore) {
        .debug %upnp.debugparamsbefore
        unset %upnp.debugbefore
        unset %upnp.debugparamsbefore
      }
    }  
  }
  return
}
alias dcc {
  if ($isid) return
  if ($istok(send:chat,$1,58)) && (!$upnp.debugpermanent) {
    if ($debug != $null) {
      set %upnp.debugparamsbefore %upnp.debugparams      
      .debug off
      set %upnp.debugbefore $true
    }
    unset %upnp.dccdebuguse
    .debug -i upnp.dcc upnp.dccdebug
    set %upnp.debuguse $true
  }
  if ($gettok($debug,-1,92) != upnp.dcc) && ($upnp.debugpermanent) {
    .debug -i upnp.dcc upnp.dccdebug
  }
  dcc $1-
}
alias upnp.start {
  unset %upnp.*
  var %upnp.discip = 239.255.255.250
  var %upnp.disclocalport = $rand(10000,63999)
  while (!$portfree(%upnp.disclocalport)) {
    %upnp.disclocalport = $rand(10000,63999)
  }
  echo $color(other) -gs -UPnP- Searching for Internet Gateway...
  sockclose upnp.discover
  sockudp -kt upnp.discover %upnp.disclocalport %upnp.discip 1900 $+(M-SEARCH * HTTP/1.1,$crlf,Host: 239.255.255.250:1900,$crlf,Man: "ssdp:discover",$crlf,ST: urn:schemas-upnp-org:service:WANIPConnection:1,$crlf,MX: 2,$str($crlf,2))
  sockudp -kt upnp.discover %upnp.disclocalport %upnp.discip 1900
  sockudp -kt upnp.discover %upnp.disclocalport %upnp.discip 1900 $+(M-SEARCH * HTTP/1.1,$crlf,Host: 239.255.255.250:1900,$crlf,Man: "ssdp:discover",$crlf,ST: urn:schemas-upnp-org:service:WANPPPConnection:1,$crlf,MX: 2,$str($crlf,2))
  sockudp -kt upnp.discover %upnp.disclocalport %upnp.discip 1900
}
alias upnp.getLocalInfo {
  set %upnp.localprevip $ip
  set %upnp.localprevhost $host
  unset %upnp.localticks  
  .localinfo -h
  .timerupnp.localip off
  set %upnp.localtimeout 5000
  set %upnp.localticks $ticks
  .timerupnp.localip 0 0 upnp.getLocalInfo.2
}
alias upnp.getLocalInfo.2 {  
  set %upnp.localip $ip
  if (%upnp.localip == $null) {
    if ($calc($ticks - %upnp.localticks) > %upnp.localtimeout) {
      echo $color(other) -gs -UPnP- Local IP not found
      .timerupnp.localip off      
      if (%upnp.localprevip != $null) {
        .localinfo $iif(%upnp.localprevhost == $null,%upnp.localprevip,%upnp.localprevhost) %upnp.localprevip
      }
    }
  }
  else {
    echo $color(other) -gs -UPnP- Local IP: $ip
    .timerupnp.localip off
    if (%upnp.localprevip != $null) {
      .localinfo $iif(%upnp.localprevhost == $null,%upnp.localprevip,%upnp.localprevhost) %upnp.localprevip
    }
  }
}
on *:udpread:upnp.discover: {
  if ($sockerr > 0) return
  sockread %info
  while ($sockbr > 0) {
    tokenize 32 %info
    if ($1 == $null) {
      echo $color(other) -gs -UPnP- Internet Gateway with %upnp.st service found at $sock($sockname).saddr
      upnp.getLocalInfo
      upnp.getDeviceInfo %upnp.discURL
      sockclose $sockname
      return
    }
    if ($gettok($1,1,58) == LOCATION) set %upnp.discURL $remove($gettok($1-,2-,58),$chr(32))
    if ($gettok($1,1,58) == ST) set %upnp.st $remove($gettok($1-,2-,58),$chr(32))
    sockread %info
  }
}
alias upnp.getDeviceInfo {
  echo $color(other) -gs -UPnP- Getting Service Info...
  var %upnp.devip = $gettok($gettok($$1,2,47),1,58)
  var %upnp.devport = $gettok($gettok($1,2,47),2,58)
  sockclose upnp.devinfo
  sockopen upnp.devinfo %upnp.devip %upnp.devport
  sockmark upnp.devinfo $+($chr(47),$gettok($1,3-,47))
}

on 1:sockopen:upnp.devinfo: {
  if ($sockerr > 0) return
  set %upnp.devinfoMode HTTP
  set %upnp.getCURL $false
  sockwrite -nt $sockname GET $sock($sockname).mark HTTP/1.1
  sockwrite -nt $sockname Host: $+($sock($sockname).ip,$chr(58),$sock($sockname).port)
  sockwrite -nt $sockname Connection: close
  sockwrite -nt $sockname Accept-Language: en
  sockwrite -nt $sockname
}
on 1:sockread:upnp.devinfo: {
  if ($sockerr > 0) return
  sockread %info
  while ($sockbr > 0) {
    tokenize 32 %info
    if (%upnp.devinfoMode == XML) {
      if (<URLBase> isin $1-) {
        set %upnp.servinfoBase $upnp.getXMLValue($1-,<URLBase>)
        echo $color(other) -gs -UPnP- Service Base URL: %upnp.servinfoBase
      }
      if (!%upnp.getCURL) {
        if ($+(<serviceType>,%upnp.st,</serviceType>) isin $1-) set %upnp.getCURL $true
      }
      if (%upnp.getCURL) { 
        if (<controlURL> isin $1-) {          
          set %upnp.servinfoURL $upnp.getXMLValue($1-,<controlURL>)
          set %upnp.getCURL $false
          echo $color(other) -gs -UPnP- Service Control URL: %upnp.servinfoURL
          upnp.getServiceExternalIP $+(%upnp.servinfoBase,%upnp.servinfoURL)
        }
      }
    }
    if ($1 == $null) && (%upnp.devinfoMode == HTTP) set %upnp.devinfoMode XML
    sockread %info
  }
}
alias upnp.getServiceExternalIP {
  echo $color(other) -gs -UPnP- Getting Service External IP Address...
  var %upnp.servip = $gettok($gettok($$1,2,47),1,58)
  var %upnp.servport = $gettok($gettok($1,2,47),2,58)
  sockclose upnp.servIP
  sockopen upnp.servIP %upnp.servip %upnp.servport
  sockmark upnp.servIP $+($chr(47),$gettok($1,3-,47))
}

on 1:sockopen:upnp.servIP: {
  if ($sockerr > 0) return
  set %upnp.servIPMode HTTP
  sockwrite -nt $sockname POST $sock($sockname).mark HTTP/1.1
  sockwrite -nt $sockname Host: $+($sock($sockname).ip,$chr(58),$sock($sockname).port)
  sockwrite -nt $sockname Connection: close
  sockwrite -nt $sockname Content-Length: $iif(PPP isin %upnp.st,273,272)
  sockwrite -nt $sockname Content-Type: text/xml; charset="utf-8"
  sockwrite -nt $sockname SOAPAction: $+(",%upnp.st,#GetExternalIPAddress")
  sockwrite -nt $sockname
  sockwrite -nt $sockname <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  sockwrite -nt $sockname <s:Body>
  sockwrite -nt $sockname $+(<u:GetExternalIPAddress xmlns:u=",%upnp.st,"></u:GetExternalIPAddress>)
  sockwrite -nt $sockname </s:Body>
  sockwrite -nt $sockname </s:Envelope>
  sockwrite -nt $sockname
}
on 1:sockread:upnp.servIP: {
  if ($sockerr > 0) return
  sockread %info
  while ($sockbr > 0) {
    tokenize 32 %info
    if (%upnp.servIPMode == XML) {
      if (<NewExternalIPAddress> isin $1-) {
        set %upnp.externalIP $upnp.getXMLValue($1-,<NewExternalIPAddress>)
        echo $color(other) -gs -UPnP- Service External IP Address: %upnp.externalIP
      }
    }
    if ($1 == $null) && (%upnp.servIPMode == HTTP) set %upnp.servIPMode XML
    sockread %info
  }
}
alias upnp.addPortMapping {
  var %upnp.lp = $iif($0 > 1,$2,$$1)
  var %upnp.curl = $iif($0 > 1,$1,$+(%upnp.servinfoBase,%upnp.servinfoURL))
  if (%upnp.localip == $null) {
    echo $color(other) -gs -UPnP- Cannot map port %upnp.lp $+ . Local IP Address is unknown
    return
  }
  echo $color(other) -gs -UPnP- Mapping port %upnp.lp to %upnp.localip $+ ...
  var %upnp.pmi = $gettok($gettok(%upnp.curl,2,47),1,58)
  var %upnp.pmp = $gettok($gettok(%upnp.curl,2,47),2,58)
  set %upnp.pmport %upnp.lp
  set %upnp.pmlease 0
  sockclose upnp.pm
  sockopen upnp.pm %upnp.pmi %upnp.pmp
  sockmark upnp.pm $+($chr(47),$gettok(%upnp.curl,3-,47))
}

on 1:sockopen:upnp.pm: {
  if ($sockerr > 0) return
  set %upnp.pmMode HTTP
  sockwrite -nt $sockname POST $sock($sockname).mark HTTP/1.1
  sockwrite -nt $sockname Host: $+($sock($sockname).ip,$chr(58),$sock($sockname).port)
  sockwrite -nt $sockname Connection: close
  sockwrite -nt $sockname Content-Length: $calc(533 + $len(%upnp.st) + ($len(%upnp.pmport) * 4) + ($len(%upnp.localip) * 2) + $len(%upnp.pmlease))
  sockwrite -nt $sockname Content-Type: text/xml; charset="utf-8"
  sockwrite -nt $sockname SOAPAction: $+(",%upnp.st,#AddPortMapping")
  sockwrite -nt $sockname
  sockwrite -nt $sockname <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  sockwrite -nt $sockname <s:Body>
  sockwrite -nt $sockname $+(<u:AddPortMapping xmlns:u=",%upnp.st,">)
  sockwrite -nt $sockname <NewRemoteHost></NewRemoteHost>
  sockwrite -nt $sockname $+(<NewExternalPort>,%upnp.pmport,</NewExternalPort>)
  sockwrite -nt $sockname <NewProtocol>TCP</NewProtocol>
  sockwrite -nt $sockname $+(<NewInternalPort>,%upnp.pmport,</NewInternalPort>)
  sockwrite -nt $sockname $+(<NewInternalClient>,%upnp.localip,</NewInternalClient>)
  sockwrite -nt $sockname <NewEnabled>1</NewEnabled>
  sockwrite -nt $sockname $+(<NewPortMappingDescription>mIRC $chr(40),%upnp.localip,:,%upnp.pmport,$chr(41),$chr(32),%upnp.pmport,$chr(32),TCP</NewPortMappingDescription>)
  sockwrite -nt $sockname $+(<NewLeaseDuration>,%upnp.pmlease,</NewLeaseDuration>)
  sockwrite -nt $sockname </u:AddPortMapping>
  sockwrite -nt $sockname </s:Body>
  sockwrite -nt $sockname </s:Envelope>
  sockwrite -nt $sockname
}
on 1:sockread:upnp.pm: {
  if ($sockerr > 0) return
  sockread %info
  while ($sockbr > 0) {
    tokenize 32 %info
    if (%upnp.pmMode == HTTP) {
      if (HTTP/1.1 200 * iswm $1-) {
        echo $color(other) -gs -UPnP- Port Mapping Added: $+(mIRC $chr(40),%upnp.localip,:,%upnp.pmport,$chr(41),$chr(32),%upnp.pmport,$chr(32),TCP,$chr(32))
        sockclose $sockname
        return
      }
      if (HTTP/1.1 500 * iswm $1-) {
        echo $color(other) -gs -UPnP- Port Mapping Add Error
        sockclose $sockname
        return
      }
      if ($1 == $null) set %upnp.pmMode XML
    }
    if (%upnp.pmMode == XML) {
      ;if ($+(<u:AddPortMappingResponse xmlns:u=",%upnp.st,"/>) isin $1-) {
      ;}
    }
    sockread %info
  }
}
alias upnp.delPortMapping {
  var %upnp.lp = $iif($0 > 1,$2,$$1)
  var %upnp.curl = $iif($0 > 1,$1,$+(%upnp.servinfoBase,%upnp.servinfoURL))
  echo $color(other) -gs -UPnP- Deleting Port Mapping for port %upnp.lp $+ ...
  var %upnp.pmdi = $gettok($gettok(%upnp.curl,2,47),1,58)
  var %upnp.pmdp = $gettok($gettok(%upnp.curl,2,47),2,58)
  set %upnp.pmdport %upnp.lp
  sockclose upnp.pmd
  sockopen upnp.pmd %upnp.pmdi %upnp.pmdp
  sockmark upnp.pmd $+($chr(47),$gettok(%upnp.curl,3-,47))
}

on 1:sockopen:upnp.pmd: {
  if ($sockerr > 0) return
  set %upnp.pmdMode HTTP
  sockwrite -nt $sockname POST $sock($sockname).mark HTTP/1.1
  sockwrite -nt $sockname Host: $+($sock($sockname).ip,$chr(58),$sock($sockname).port)
  sockwrite -nt $sockname Connection: close
  sockwrite -nt $sockname Content-Length: $calc(324 + $len(%upnp.st) + $len(%upnp.pmport))
  sockwrite -nt $sockname Content-Tupe: text/xml; charset="utf-8"
  sockwrite -nt $sockname SOAPAction: $+(",%upnp.st,#DeletePortMapping")
  sockwrite -nt $sockname
  sockwrite -nt $sockname <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  sockwrite -nt $sockname <s:Body>
  sockwrite -nt $sockname $+(<u:DeletePortMapping xmlns:u=",%upnp.st,">)
  sockwrite -nt $sockname <NewRemoteHost></NewRemoteHost>
  sockwrite -nt $sockname $+(<NewExternalPort>,%upnp.pmdport,</NewExternalPort>)
  sockwrite -nt $sockname <NewProtocol>TCP</NewProtocol>
  sockwrite -nt $sockname </u:DeletePortMapping>
  sockwrite -nt $sockname </s:Body>
  sockwrite -nt $sockname </s:Envelope>
  sockwrite -nt $sockname
}
on 1:sockread:upnp.pmd: {
  if ($sockerr > 0) return
  sockread %info
  while ($sockbr > 0) {
    tokenize 32 %info
    if (%upnp.pmdMode == HTTP) {
      if (HTTP/1.1 200 * iswm $1-) {
        echo $color(other) -gs -UPnP- Port Mapping Deleted for port %upnp.pmdport
        sockclose $sockname
        return
      }
      if (HTTP/1.1 500 * iswm $1-) {
        echo $color(other) -gs -UPnP- Port Mapping Delete Error
        sockclose $sockname
        return
      }
      if ($1 == $null) set %upnp.pmdMode XML
    }
    if (%upnp.pmdMode == XML) {
      ;if ($+(<u:DeletePortMappingResponse xmlns:u=",%upnp.st,"/>) isin $1-) {
      ;}
    }
    sockread %info
  }
}
alias upnp.getXMLValue {
  if (!$isid) return  
  var %rest = $right($$1-,$calc(0 - ($pos($1-,$2) + $len($2) - 1)))
  var %tag = $2
  if ($pos(%rest,<) != 1) return $gettok(%rest,1,$asc(<))
  else return $null
}

The "upnp.addPortMapping <port>" and "upnp.delPortMapping <port>" are the two aliases that do the "magic".

Please note that running the initialization commands is VITAL.

I added a nice little feature:

- If $upnp.debugpermanent (on top of the code) returns $true, then the script will take over and need to be using the output of /debug the whole time.

- If $upnp.debugpermanent returns $false, the script will only take over /debug while waiting for the DCC message to be sent (in order to capture the port) and then it will restore /debug to its previous state (if it was activated), which allows it to coexist with other scripts that use /debug. When using this mode, you cannot use the "Resend" button after a DCC transfer fails, and you cannot use the DCC Send toolbar button to initiate a transfer, since mIRC won't trigger the /dcc custom alias.

I've tried my best to test it, but it certainly does need a lot more of testing and error checking. For example, using "recycled" DCC Send/Chat windows will generate a conflict if there is a newer DCC Send/Chat window for the same nick.

There are some things that should probably be added, like some popup menus to enable/disable it for DCC/Identd, a control list of ports that the script has open (to close them at any given time, because this script uses leases of "0", thanks to my router not supporting non-zero leases), an alias to close all the open ports (using the previous list) on EXIT so the don't stay permanently open, hash tables instead of variables, etc.

Also, the XML parser might be a little "weak".

- EDIT: Corrected a little important typo bug in "on 1:sockread:upnp.pm:"

Last edited by Strider; 07/04/07 11:23 PM.