netwerk/test/unit/test_socks.js

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 const CC = Components.Constructor;
     3 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
     4                         "nsIServerSocket",
     5                         "init");
     6 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
     7                            "nsIBinaryInputStream",
     8                            "setInputStream");
     9 const DirectoryService = CC("@mozilla.org/file/directory_service;1",
    10                             "nsIProperties");
    11 const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
    13 const currentThread = Cc["@mozilla.org/thread-manager;1"]
    14                       .getService().currentThread;
    16 var socks_test_server = null;
    17 var socks_listen_port = -1;
    19 function getAvailableBytes(input)
    20 {
    21   var len = 0;
    23   try {
    24     len = input.available();  
    25   } catch (e) {
    26   }
    28   return len;
    29 }
    31 function runScriptSubprocess(script, args)
    32 {
    33   // logic copied from ted's crashreporter unit test
    34   var ds = new DirectoryService();
    35   var bin = ds.get("CurProcD", Ci.nsILocalFile);
    37   bin.append("xpcshell");
    38   if (!bin.exists()) {
    39     bin.leafName = "xpcshell.exe";
    40     do_check_true(bin.exists());
    41     if (!bin.exists())
    42       do_throw("Can't find xpcshell binary");
    43   }
    45   var script = do_get_file(script);
    46   var proc = new Process(bin);
    47   var args = [script.path].concat(args);
    49   proc.run(false, args, args.length);
    51   return proc;
    52 }
    54 function buf2ip(buf)
    55 {
    56   if (buf.length == 16) {
    57     var ip = (buf[0]  << 4 | buf[1]).toString(16) + ':' +
    58              (buf[2]  << 4 | buf[3]).toString(16) + ':' +
    59              (buf[4]  << 4 | buf[5]).toString(16) + ':' +
    60              (buf[6]  << 4 | buf[7]).toString(16) + ':' +
    61              (buf[8]  << 4 | buf[9]).toString(16) + ':' +
    62              (buf[10] << 4 | buf[11]).toString(16) + ':' +
    63              (buf[12] << 4 | buf[13]).toString(16) + ':' +
    64              (buf[14] << 4 | buf[15]).toString(16);
    65     for (var i = 8; i >= 2; i--) {
    66       var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
    67       var shortip = ip.replace(re, '::');
    68       if (shortip != ip) {
    69         return shortip;
    70       }
    71     }
    72     return ip;
    73   } else {
    74     return buf.join('.');
    75   }
    76 }
    78 function buf2int(buf)
    79 {
    80   var n = 0;
    82   for (var i in buf) {
    83     n |= buf[i] << ((buf.length - i - 1) * 8);
    84   }
    86   return n;
    87 }
    89 function buf2str(buf)
    90 {
    91   return String.fromCharCode.apply(null, buf);
    92 }
    94 const STATE_WAIT_GREETING = 1;
    95 const STATE_WAIT_SOCKS4_REQUEST = 2;
    96 const STATE_WAIT_SOCKS4_USERNAME = 3;
    97 const STATE_WAIT_SOCKS4_HOSTNAME = 4;
    98 const STATE_WAIT_SOCKS5_GREETING = 5;
    99 const STATE_WAIT_SOCKS5_REQUEST = 6;
   100 const STATE_WAIT_PONG = 7;
   101 const STATE_GOT_PONG = 8;
   103 function SocksClient(server, client_in, client_out)
   104 {
   105   this.server = server;
   106   this.type = '';
   107   this.username = '';
   108   this.dest_name = '';
   109   this.dest_addr = [];
   110   this.dest_port = [];
   112   this.client_in = client_in;
   113   this.client_out = client_out;
   114   this.inbuf = [];
   115   this.outbuf = String();
   116   this.state = STATE_WAIT_GREETING;
   117   this.waitRead(this.client_in);
   118 }
   119 SocksClient.prototype = {
   120   onInputStreamReady: function(input)
   121   {
   122     var len = getAvailableBytes(input);
   124     if (len == 0) {
   125       print('server: client closed!');
   126       do_check_eq(this.state, STATE_GOT_PONG);
   127       this.server.testCompleted(this);
   128       return;
   129     }
   131     var bin = new BinaryInputStream(input);
   132     var data = bin.readByteArray(len);
   133     this.inbuf = this.inbuf.concat(data);
   135     switch (this.state) {
   136       case STATE_WAIT_GREETING:
   137         this.checkSocksGreeting();
   138         break;
   139       case STATE_WAIT_SOCKS4_REQUEST:
   140         this.checkSocks4Request();
   141         break;
   142       case STATE_WAIT_SOCKS4_USERNAME:
   143         this.checkSocks4Username();
   144         break;
   145       case STATE_WAIT_SOCKS4_HOSTNAME:
   146         this.checkSocks4Hostname();
   147         break;
   148       case STATE_WAIT_SOCKS5_GREETING:
   149         this.checkSocks5Greeting();
   150         break;
   151       case STATE_WAIT_SOCKS5_REQUEST:
   152         this.checkSocks5Request();
   153         break;
   154       case STATE_WAIT_PONG:
   155         this.checkPong();
   156         break;
   157       default:
   158         do_throw("server: read in invalid state!");
   159     }
   161     this.waitRead(input);
   162   },
   164   onOutputStreamReady: function(output)
   165   {
   166     var len = output.write(this.outbuf, this.outbuf.length);
   167     if (len != this.outbuf.length) {
   168       this.outbuf = this.outbuf.substring(len);
   169       this.waitWrite(output);
   170     } else
   171       this.outbuf = String();
   172   },
   174   waitRead: function(input)
   175   {
   176     input.asyncWait(this, 0, 0, currentThread);
   177   },
   179   waitWrite: function(output)
   180   {
   181     output.asyncWait(this, 0, 0, currentThread);
   182   },
   184   write: function(buf)
   185   {
   186     this.outbuf += buf;
   187     this.waitWrite(this.client_out);
   188   },
   190   checkSocksGreeting: function()
   191   {
   192     if (this.inbuf.length == 0)
   193       return;
   195     if (this.inbuf[0] == 4) {
   196       print('server: got socks 4');
   197       this.type = 'socks4';
   198       this.state = STATE_WAIT_SOCKS4_REQUEST;
   199       this.checkSocks4Request();
   200     } else if (this.inbuf[0] == 5) {
   201       print('server: got socks 5');
   202       this.type = 'socks';
   203       this.state = STATE_WAIT_SOCKS5_GREETING;
   204       this.checkSocks5Greeting();
   205     } else {
   206       do_throw("Unknown socks protocol!");
   207     }
   208   },
   210   checkSocks4Request: function()
   211   {
   212     if (this.inbuf.length < 8)
   213       return;
   215     do_check_eq(this.inbuf[1], 0x01);
   217     this.dest_port = this.inbuf.slice(2, 4);
   218     this.dest_addr = this.inbuf.slice(4, 8);
   220     this.inbuf = this.inbuf.slice(8);
   221     this.state = STATE_WAIT_SOCKS4_USERNAME;
   222     this.checkSocks4Username();
   223   },
   225   readString: function()
   226   {
   227     var i = this.inbuf.indexOf(0);
   228     var str = null;
   230     if (i >= 0) {
   231       var buf = this.inbuf.slice(0,i);
   232       str = buf2str(buf);
   233       this.inbuf = this.inbuf.slice(i+1);
   234     }
   236     return str;
   237   },
   239   checkSocks4Username: function()
   240   {
   241     var str = this.readString();
   243     if (str == null)
   244       return;
   246     this.username = str;
   247     if (this.dest_addr[0] == 0 &&
   248         this.dest_addr[1] == 0 &&
   249         this.dest_addr[2] == 0 &&
   250         this.dest_addr[3] != 0) {
   251       this.state = STATE_WAIT_SOCKS4_HOSTNAME;
   252       this.checkSocks4Hostname();
   253     } else {
   254       this.sendSocks4Response();
   255     }
   256   },
   258   checkSocks4Hostname: function()
   259   {
   260     var str = this.readString();
   262     if (str == null)
   263       return;
   265     this.dest_name = str;
   266     this.sendSocks4Response();
   267   },
   269   sendSocks4Response: function()
   270   {
   271     this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
   272     this.sendPing();
   273   },
   275   checkSocks5Greeting: function()
   276   {
   277     if (this.inbuf.length < 2)
   278       return;
   279     var nmethods = this.inbuf[1];
   280     if (this.inbuf.length < 2 + nmethods)
   281       return;
   283     do_check_true(nmethods >= 1);  
   284     var methods = this.inbuf.slice(2, 2 + nmethods);
   285     do_check_true(0 in methods);
   287     this.inbuf = [];
   288     this.state = STATE_WAIT_SOCKS5_REQUEST;
   289     this.write('\x05\x00');
   290   },
   292   checkSocks5Request: function()
   293   {
   294     if (this.inbuf.length < 4)
   295       return;
   297     do_check_eq(this.inbuf[0], 0x05);
   298     do_check_eq(this.inbuf[1], 0x01);
   299     do_check_eq(this.inbuf[2], 0x00);
   301     var atype = this.inbuf[3];
   302     var len;
   303     var name = false;
   305     switch (atype) {
   306       case 0x01:
   307         len = 4;
   308         break;
   309       case 0x03:
   310         len = this.inbuf[4];
   311         name = true;
   312         break;
   313       case 0x04:
   314         len = 16;
   315         break;  
   316       default:
   317         do_throw("Unknown address type " + atype);
   318     }
   320     if (name) {
   321       if (this.inbuf.length < 4 + len + 1 + 2)
   322         return;
   324       buf = this.inbuf.slice(5, 5 + len);
   325       this.dest_name = buf2str(buf);
   326       len += 1;
   327     } else {
   328       if (this.inbuf.length < 4 + len + 2)
   329         return;
   331       this.dest_addr = this.inbuf.slice(4, 4 + len);
   332     }
   334     len += 4;
   335     this.dest_port = this.inbuf.slice(len, len + 2);
   336     this.inbuf = this.inbuf.slice(len + 2);
   337     this.sendSocks5Response();
   338   },
   340   sendSocks5Response: function()
   341   {
   342     if (this.dest_addr.length == 16) {
   343       // send a successful response with the address, [::1]:80
   344       this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80';
   345     } else {
   346       // send a successful response with the address, 127.0.0.1:80
   347       this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
   348     }
   349     this.sendPing();
   350   },
   352   sendPing: function()
   353   {
   354     print('server: sending ping');
   355     this.state = STATE_WAIT_PONG;
   356     this.outbuf += "PING!";
   357     this.inbuf = [];
   358     this.waitWrite(this.client_out);
   359     this.waitRead(this.client_in);
   360   },
   362   checkPong: function()
   363   {
   364     var pong = buf2str(this.inbuf);
   365     do_check_eq(pong, "PONG!");
   366     this.state = STATE_GOT_PONG;
   367     this.waitRead(this.client_in);
   368   },
   370   close: function()
   371   {
   372     this.client_in.close();
   373     this.client_out.close();
   374   }
   375 };
   377 function SocksTestServer()
   378 {
   379   this.listener = ServerSocket(-1, true, -1);
   380   socks_listen_port = this.listener.port;
   381   print('server: listening on', socks_listen_port);
   382   this.listener.asyncListen(this);
   383   this.test_cases = [];
   384   this.client_connections = [];
   385   this.client_subprocess = null;
   386   // port is used as the ID for test cases
   387   this.test_port_id = 8000;
   388   this.tests_completed = 0;
   389 }
   390 SocksTestServer.prototype = {
   391   addTestCase: function(test)
   392   {
   393     test.finished = false;
   394     test.port = this.test_port_id++;
   395     this.test_cases.push(test);
   396   },
   398   pickTest: function(id)
   399   {
   400     for (var i in this.test_cases) {
   401       var test = this.test_cases[i];
   402       if (test.port == id) {
   403         this.tests_completed++;
   404         return test;
   405       }
   406     }
   407     do_throw("No test case with id " + id);
   408   },
   410   testCompleted: function(client)
   411   {
   412     var port_id = buf2int(client.dest_port);
   413     var test = this.pickTest(port_id);
   415     print('server: test finished', test.port);
   416     do_check_true(test != null);
   417     do_check_eq(test.expectedType || test.type, client.type);
   418     do_check_eq(test.port, port_id);
   420     if (test.remote_dns)
   421       do_check_eq(test.host, client.dest_name);
   422     else
   423       do_check_eq(test.host, buf2ip(client.dest_addr));
   425     if (this.test_cases.length == this.tests_completed) {
   426       print('server: all tests completed');
   427       this.close();
   428       do_test_finished();
   429     }
   430   },
   432   runClientSubprocess: function()
   433   {
   434     var argv = [];
   436     // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
   437     for each (var test in this.test_cases) {
   438       var arg = test.type + '|' +
   439         String(socks_listen_port) + '|' +
   440         test.host + '|' + test.port + '|';
   441       if (test.remote_dns)
   442         arg += 'remote';
   443       else
   444         arg += 'local';
   445       print('server: using test case', arg);  
   446       argv.push(arg);      
   447     }
   449     this.client_subprocess = runScriptSubprocess(
   450         'socks_client_subprocess.js', argv);
   451   },
   453   onSocketAccepted: function(socket, trans)
   454   {
   455     print('server: got client connection');
   456     var input = trans.openInputStream(0, 0, 0);
   457     var output = trans.openOutputStream(0, 0, 0);
   458     var client = new SocksClient(this, input, output);
   459     this.client_connections.push(client);
   460   },
   462   onStopListening: function(socket)
   463   {  
   464   },
   466   close: function()
   467   {
   468     if (this.client_subprocess) {
   469       try {
   470         this.client_subprocess.kill();      
   471       } catch (x) {
   472         do_note_exception(x, 'Killing subprocess failed');
   473       }
   474       this.client_subprocess = null;
   475     }
   476     for each (var client in this.client_connections)
   477       client.close();
   478     this.client_connections = [];
   479     if (this.listener) {
   480       this.listener.close();
   481       this.listener = null;
   482     }
   483   }
   484 };
   486 function test_timeout()
   487 {
   488   socks_test_server.close();
   489   do_throw("SOCKS test took too long!");
   490 }
   492 function run_test()
   493 {
   494   socks_test_server = new SocksTestServer();
   496   socks_test_server.addTestCase({
   497         type: "socks4",
   498         host: '127.0.0.1',
   499         remote_dns: false,
   500   });
   501   socks_test_server.addTestCase({
   502         type: "socks4",
   503         host: '12345.xxx',
   504         remote_dns: true,
   505   });
   506   socks_test_server.addTestCase({
   507         type: "socks4",
   508         expectedType: "socks",
   509         host: '::1',
   510         remote_dns: false,
   511   });
   512   socks_test_server.addTestCase({
   513         type: "socks",
   514         host: '127.0.0.1',
   515         remote_dns: false,
   516   });
   517   socks_test_server.addTestCase({
   518         type: "socks",
   519         host: 'abcdefg.xxx',
   520         remote_dns: true,
   521   });
   522   socks_test_server.addTestCase({
   523         type: "socks",
   524         host: '::1',
   525         remote_dns: false,
   526   });
   527   socks_test_server.runClientSubprocess();
   529   do_timeout(120 * 1000, test_timeout);
   530   do_test_pending();
   531 }

mercurial