Created
November 24, 2008 13:37
-
-
Save ymnk/28465 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.net.{InetAddress, Socket, InetSocketAddress} | |
| import java.io._ | |
| import scala.collection.mutable._ | |
| import scala.io.Source | |
| import scala.concurrent.ops.spawn | |
| /** | |
| * This program expects to be comipled with ARCFour.scala[1] . | |
| * [1] http://gist.github.com/28462 | |
| */ | |
| class WinnyARCFour extends ARCFour{ | |
| private def chop(key:Array[Byte])={ | |
| key.indexOf(0) match { | |
| case -1 => key | |
| case 0 => new Array[Byte](1) | |
| case i => key.subArray(0, i) | |
| } | |
| } | |
| override def init(key:Array[Byte]) = super.init(chop(key)) | |
| } | |
| object WinnyNode{ | |
| private val key=" piewf6ascxlv".getBytes | |
| private val ipport="([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):([0-9]+)".r | |
| /** | |
| * The node has the following form, for example, | |
| * @5e4d8e761fb09aaed8857e9397c93bae4617 | |
| * and it will be decoded to the pair of ip address and port. | |
| */ | |
| def decode(node:String):Option[(InetAddress, Int)] = key.synchronized{ | |
| val src = toArray(node.substring(1)) | |
| key(0) = src(0) | |
| val dest = { | |
| val cipher = new WinnyARCFour | |
| cipher.init(key) | |
| cipher.decrypt(src, 1, src.length-1) | |
| } | |
| if((checkSum(dest)&0xff) == (src(0)&0xff)){ | |
| new String(dest) match { | |
| case ipport(ip, port) if !ip.startsWith("192.168.") => | |
| try{ | |
| Some((InetAddress.getByName(ip) -> Integer.parseInt(port))) | |
| } | |
| catch{ case e => None } | |
| case _ => None | |
| } | |
| } | |
| else None | |
| } | |
| private def toArray(hex:String):Array[Byte]={ | |
| var i=0 | |
| (new Array[Int](hex.length/2) /: hex){ (buf,b) => { | |
| buf(i/2)<<=4 | |
| buf(i/2)|=(if('0'<=b&&b<='9') b-'0' else b-'a'+10) | |
| i+=1 | |
| buf | |
| } | |
| }.map(i=>(i&0xff).asInstanceOf[Byte]) | |
| } | |
| private def checkSum(buf:Array[Byte]) = (0 /: buf){(s,i)=>s+(i&0xff)} | |
| } | |
| object WinnyPool extends Iterator[(InetAddress, Int)]{ | |
| private val pool=new Stack[(InetAddress, Int)] | |
| private val done=Set.empty[(InetAddress, Int)] | |
| def += (node:String){ | |
| for(n <- WinnyNode.decode(node)) | |
| this+=(n._1, n._2) | |
| } | |
| def += (ip:InetAddress, port:Int)=synchronized{ | |
| if(!ip.toString.startsWith("/192.168") && | |
| !pool.contains((ip, port)) && | |
| !done.contains((ip, port))){ | |
| // println("WinnyPool += "+ip+":"+port) | |
| pool+=(ip, port) | |
| } | |
| } | |
| def -= (ip:InetAddress, port:Int) = synchronized{ done -= (ip -> port) } | |
| def hasNext = synchronized{ | |
| if(!pool.isEmpty) true | |
| else if(!done.isEmpty){ done.foreach{pool.push(_)}; done.clear; true} | |
| else false | |
| } | |
| def next = synchronized{ | |
| if(!pool.isEmpty){ | |
| pool.pop match{ | |
| case p => done += p; p | |
| } | |
| } | |
| else null | |
| } | |
| } | |
| abstract class WinnyIO { | |
| private val key=new Array[Byte](4) | |
| private val cipher_in = new WinnyARCFour | |
| private val mask=Array(0, 0x39, 0x39, 0x39).map(_.asInstanceOf[Byte]) | |
| private def socket(ip:InetAddress, port:Int):Option[Socket] = try{ | |
| Some(new Socket match { | |
| case s => { | |
| s.connect(new InetSocketAddress(ip, port), 3*1000); | |
| s.setSoTimeout(10*1000) | |
| s | |
| } | |
| } | |
| ) | |
| } | |
| catch { | |
| case e=> { | |
| WinnyPool -= (ip, port) | |
| None | |
| } | |
| } | |
| def swap(b:Array[Byte]) = b.foldRight(0){(b, result)=>(result<<8)+(b&0xff)} | |
| implicit def toDataInputStream(i:InputStream) = | |
| new DataInputStream(i){ | |
| def readBytes(n:Int)={ | |
| if((n & ~(0xffff))!=0) | |
| throw new IOException("attack?") | |
| val buf=new Array[Byte](n) | |
| readFully(buf) | |
| buf | |
| } | |
| } | |
| def connect(ip:InetAddress, port:Int) = try { | |
| for(socket<-socket(ip, port)){ | |
| val (in, out) = (socket.getInputStream, socket.getOutputStream) | |
| try{ | |
| in.readChar | |
| in.readFully(key) | |
| cipher_in.init(key) | |
| def nextData = { | |
| val len = swap(cipher_in.decrypt(in.readBytes(4), 0, 4)) | |
| val d=cipher_in.decrypt(in.readBytes(len), 0, len) | |
| if(d(0)==0){ | |
| cipher_in.init((key zip mask).map{ | |
| case (k,m) => ((k&0xff)^m).asInstanceOf[Byte]}) | |
| } | |
| d | |
| } | |
| var loop=true | |
| while(loop){ | |
| val data = nextData | |
| commandMap.get(data(0)) match { | |
| case Some(c) => c.proc(data) | |
| case _ => | |
| } | |
| } | |
| } | |
| finally{ | |
| in.close | |
| out.close | |
| } | |
| } | |
| }catch{case e => } | |
| abstract class Command { | |
| def toInputStream(buf:Array[Byte]) = new ByteArrayInputStream(buf) | |
| def proc(buf:Array[Byte]) | |
| } | |
| private val commandMap = Map.empty[Int, Command] | |
| def addCommand(i:Int, c:Command) = commandMap.synchronized{ | |
| commandMap += (i -> c) | |
| } | |
| } | |
| class FilesOnWinny_ extends WinnyIO { | |
| object Command13 extends Command { | |
| def proc(buf:Array[Byte]){ | |
| var buf_length = buf.length | |
| val in = toInputStream(buf) | |
| in.readByte; buf_length -= 1 | |
| val response_flag=in.readByte&0xff | |
| val diff_flag=in.readByte&0xff | |
| val down_flag=in.readByte&0xff | |
| val bbs_flag=in.readByte&0xff | |
| val query_id=in.readInt | |
| val keyword_length=in.readByte&0xff | |
| val keyword=in.readBytes(keyword_length); | |
| buf_length -= (9+keyword_length) | |
| val trip=in.readBytes(11); buf_length -= 11 | |
| //println("keyword: "+new String(keyword, "Windows-31J")) | |
| val node_count=in.readByte&0xff; buf_length -= 1 | |
| val hosts=HashSet.empty[(InetAddress, Int)] | |
| for(_ <- (0 until node_count)){ | |
| val h=InetAddress.getByAddress(in.readBytes(4)) | |
| val p=swap(in.readBytes(4))&0xffff | |
| hosts += (h -> p) ; buf_length -= 8 | |
| WinnyPool += (h, p) | |
| } | |
| if(buf_length==0) | |
| return | |
| for(_ <- (0 until swap(in.readBytes(2)))){ | |
| val kh=InetAddress.getByAddress(in.readBytes(4)) | |
| val kp=swap(in.readBytes(2)) | |
| val bh=InetAddress.getByAddress(in.readBytes(4)) | |
| val bp=swap(in.readBytes(2)) | |
| val fs=swap(in.readBytes(4)) | |
| val fh=in.readBytes(16) | |
| val fnl=in.readByte&0xff | |
| val fnk1=in.readBytes(1) | |
| val fnk2=in.readBytes(1) | |
| var fn = new WinnyARCFour match { | |
| case c => c.init(fnk1); c.decrypt(in.readBytes(fnl), 0, fnl) | |
| } | |
| val ft=in.readBytes(11) | |
| val bal=in.readByte&0xff | |
| val kt=in.readChar&0xffff | |
| val rb=in.readInt | |
| val ut=in.readInt | |
| val igf=in.readByte&0xff | |
| val kv=in.readByte&0xff | |
| WinnyPool += (kh, kp) | |
| println(new String(fn, "Windows-31J")) | |
| } | |
| } | |
| } | |
| addCommand(13, Command13) | |
| } | |
| /** | |
| * The "main" method expects files or URIs, which includes winny nodes | |
| * par each line. | |
| */ | |
| object FilesOnWinny { | |
| def main(arg:Array[String])={ | |
| import Source.{fromFile, fromURL} | |
| def toSource(f:String) = | |
| if(f.startsWith("http://")) fromURL(f) else fromFile(f) | |
| for(i<-arg.map(toSource(_)); | |
| j<-i.mkString("").split("\n").reverse if (j.length %2)==1) | |
| WinnyPool += j | |
| (0 to 3).foreach{ _ => spawn{ | |
| for(w <- WinnyPool){ | |
| new FilesOnWinny_().connect(w._1, w._2) | |
| try{ Thread.sleep(1000) }catch{case e => } | |
| }}} | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment