tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_socks.js (12109B)


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