From f856df52223e674ca479ff58c5c4f54b6d836fc9 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Sun, 12 Jul 2015 15:38:13 -0400 Subject: [PATCH 1/2] Fix #460 using OS APIs --- chrome/content/zotero/xpcom/dns_worker.js | 233 ++++++++++++++++++++++ chrome/content/zotero/xpcom/proxy.js | 144 +++---------- 2 files changed, 263 insertions(+), 114 deletions(-) create mode 100644 chrome/content/zotero/xpcom/dns_worker.js diff --git a/chrome/content/zotero/xpcom/dns_worker.js b/chrome/content/zotero/xpcom/dns_worker.js new file mode 100644 index 000000000..4441f8347 --- /dev/null +++ b/chrome/content/zotero/xpcom/dns_worker.js @@ -0,0 +1,233 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2015 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + +function getIPForLookup(ip) { + if (ip.indexOf(".") != -1) { + // IPv4 + x = ip.split(".").reverse().join(".")+".in-addr.arpa"; + } else { + if (ip.indexOf("%") != -1) ip = ip.substr(0, ip.indexOf("%")); + // IPv6 + var parts = ip.split(":"); + x = "ip6.arpa" + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + for (var j = 0; j < (part.length == 0 ? 4*(9-parts.length) : 4-part.length); j++) x = "0." + x; + for (var j = 0; j < part.length; j++) x = part[j] + "." + x; + } + } + return x; +} + +function isLocalIP(ip) { + return ip.startsWith("169.254.") || ip.startsWith("192.168.") || ip.startsWith("10.") || + /^172\.(?:1[6-9]|2[0-9]|3[01])\./.test(ip) || + ip.startsWith("fe80:") || ip.startsWith("fd00:") || ip == ""; +} + +onmessage = function (e) { + var libc, reverseLookup, getIPs, getnameinfo; + var sockaddr = new ctypes.StructType("sockaddr"); + platform = e.data; + + if (platform == "win") { + libc = ctypes.open("Ws2_32.dll"); + var addrinfo = new ctypes.StructType("arrinfo"); + addrinfo.define([{"ai_flags":ctypes.int}, {"ai_family":ctypes.int}, {"ai_socktype":ctypes.int}, + {"ai_protocol":ctypes.int}, {"ai_addrlen":ctypes.int}, {"ai_canonname":ctypes.char.ptr}, + {"ai_addr":sockaddr.ptr}, {"ai_next":addrinfo.ptr}]); + var gethostname = libc.declare("gethostname", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.size_t); + var getaddrinfo = libc.declare("getaddrinfo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.char.ptr, + addrinfo.ptr, addrinfo.ptr.ptr); + var freeaddrinfo = libc.declare("freeaddrinfo", ctypes.default_abi, ctypes.void_t, addrinfo.ptr); + getnameinfo = libc.declare("getnameinfo", ctypes.default_abi, ctypes.int, sockaddr.ptr, ctypes.int, + ctypes.char.ptr, ctypes.int, ctypes.char.ptr, ctypes.int, ctypes.int); + getIPs = function () { + var buf = new new ctypes.ArrayType(ctypes.char, 1025); + var status = gethostname(buf, 1025); + if (status != 0) throw new Error("could not get hostname: "+status); + + var ips = []; + var out = new addrinfo.ptr(); + status = getaddrinfo(buf, null, null, out.address()); + if (status != 0) throw new Error("could not get addrinfo: "+status); + var rec = out; + try { + while (!rec.isNull()) { + status = getnameinfo(rec.contents.ai_addr, rec.contents.ai_addrlen, buf, 1025, null, 0, 2); + if (status != 0) throw new Error("could not get IP address: "+status); + var ip = buf.readString(); + if (!isLocalIP(ip)) ips.push(ip); + rec = rec.contents.ai_next; + } + } finally { + freeaddrinfo(out); + } + return ips; + }; + + var dnsapi = ctypes.open("Dnsapi.dll"); + var DNS_RECORD = new ctypes.StructType("DNS_RECORD"); + DNS_RECORD.define([{"pNext":DNS_RECORD.ptr}, {"pName":ctypes.char.ptr}, {"wType":ctypes.unsigned_short}, + {"wDataLength":ctypes.unsigned_short}, {"DW":ctypes.unsigned_long}, {"dwTtl":ctypes.unsigned_long}, + {"dwReserved":ctypes.unsigned_long}, {"pNameHost":ctypes.char.ptr}]); + var DnsQuery = dnsapi.declare("DnsQuery_A", ctypes.winapi_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_short, + ctypes.unsigned_long, ctypes.voidptr_t, DNS_RECORD.ptr, ctypes.voidptr_t); + var DnsRecordListFree = dnsapi.declare("DnsRecordListFree", ctypes.winapi_abi, ctypes.void_t, DNS_RECORD.ptr, + ctypes.int); + reverseLookup = function (ip) { + var record = new DNS_RECORD(); + var status = DnsQuery(getIPForLookup(ip), 12 /*DNS_TYPE_PTR*/, 32 /*DNS_QUERY_NO_LOCAL_NAME*/, null, record.address(), null); + if (status != 0 || record.pNext.isNull()) return null; + var retval = record.pNext.contents.pNameHost.readString(); + DnsRecordListFree(record.pNext, 1); + return retval; + }; + } else { + if (platform == "mac") { + libc = ctypes.open("libc.dylib"); + } else { + var possibleLibcs = [ + "libc.so.6", + "libc.so.6.1", + "libc.so" + ]; + for(var i = 0; i < possibleLibcs.length; i++) { + try { + libc = ctypes.open(possibleLibcs[i]); + break; + } catch(e) {} + } + } + + var AF_INET = 2, AF_INET6, NI_NUMERICHOST, sockaddr_size, libresolv; + if (platform == "linux") { + libresolv = ctypes.open("libresolv.so"); + sockaddr.define([{"sa_family":ctypes.unsigned_short}]); + sockaddrSize = function (x) { return x.sa_family == 10 ? 28 : 16; }; + AF_INET6 = 10; + NI_NUMERICHOST = 1; + } else { + libresolv = libc; + sockaddr.define([{"sa_len":ctypes.uint8_t}, {"sa_family":ctypes.uint8_t}]); + sockaddrSize = function (x) { return x.sa_len; }; + AF_INET6 = 30; + NI_NUMERICHOST = 2; + } + + var ifaddrs = new ctypes.StructType("ifaddrs"); + ifaddrs.define([{"ifa_next":ifaddrs.ptr}, {"ifa_name":ctypes.char.ptr}, {"ifa_flags":ctypes.unsigned_int}, + {"ifa_addr":sockaddr.ptr}]); + var getifaddrs = libc.declare("getifaddrs", ctypes.default_abi, ctypes.int, ifaddrs.ptr.ptr); + var freeifaddrs = libc.declare("freeifaddrs", ctypes.default_abi, ctypes.void_t, ifaddrs.ptr); + getnameinfo = libc.declare("getnameinfo", ctypes.default_abi, ctypes.int, sockaddr.ptr, ctypes.int, + ctypes.char.ptr, ctypes.int, ctypes.char.ptr, ctypes.int, ctypes.int); + getIPs = function () { + var buf = new new ctypes.ArrayType(ctypes.char, 1025); + var out = new ifaddrs.ptr(); + var status = getifaddrs(out.address()); + if (status != 0) throw new Error("could not get ifaddrs: "+status); + var ips = []; + var rec = out; + try { + while (!rec.isNull()) { + if (!rec.contents.ifa_name.readString().startsWith("lo")) { + var family = rec.contents.ifa_addr.contents.sa_family; + if (family == AF_INET || family == AF_INET6) { + status = getnameinfo(rec.contents.ifa_addr, sockaddrSize(rec.contents.ifa_addr.contents), + buf, 1025, null, 0, NI_NUMERICHOST); + if (status != 0) throw new Error("could not get IP address: "+status); + var ip = buf.readString(); + if (!isLocalIP(ip)) ips.push(ip); + } + } + rec = rec.contents.ifa_next; + } + } finally { + freeifaddrs(out); + } + return ips; + }; + + var res_query; + try { + res_query = libresolv.declare("res_query", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int, + ctypes.int, ctypes.uint8_t.ptr, ctypes.int); + } catch(e) { + res_query = libresolv.declare("__res_query", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int, + ctypes.int, ctypes.uint8_t.ptr, ctypes.int); + } + let response = new new ctypes.ArrayType(ctypes.uint8_t, 1025); + var skipName = function(response, offset) { + var len = response[offset++]; + if ((len & 192) == 192) return offset+1; // compressed + while (len != 0) { + offset += len; + len = response[offset++]; + }; + return offset; + }; + var reverseLookup = function(ip) { + var len = res_query(getIPForLookup(ip), 1, 12, response, 1025); + if (len <= 0) return null; + + var offset = 4; + var qdCount = (response[offset++] << 8) + response[offset++]; + var anCount = (response[offset++] << 8) + response[offset++]; + offset += 4; + for (var i=0; i= 1) { + offset = skipName(response, offset); + offset += 8; + var rdLength = (response[offset++] << 8) + response[offset++]; // RDLENGTH + var endOfData = offset+rdLength; + while(offset < endOfData) { + if(offset > endOfData) break; + var len = response[offset++]; + if(offset+len > endOfData) break; + var str = ""; + for(var i = 0; i < len; i++) { + str += String.fromCharCode(response[offset++]); + } + domain.push(str); + } + domain.pop(); + } + return domain.join(".") + }; + } + + var ips = getIPs(); + var hosts = []; + for (var i = 0; i < ips.length; i++) { + var host = reverseLookup(ips[i]); + if(host) hosts.push(host); + } + + postMessage(hosts); +}; \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/proxy.js b/chrome/content/zotero/xpcom/proxy.js index 6cac62dfa..f2fa2e122 100644 --- a/chrome/content/zotero/xpcom/proxy.js +++ b/chrome/content/zotero/xpcom/proxy.js @@ -166,26 +166,29 @@ Zotero.Proxies = new function() { // IP update interval is every 15 minutes if((now - Zotero.Proxies.lastIPCheck) > 900000) { - Zotero.debug("Proxies: Retrieving IPs"); - var ips = Zotero.Proxies.DNS.getIPs(); - var ipString = ips.join(","); - if(ipString != Zotero.Proxies.lastIPs) { - // if IPs have changed, run reverse lookup - Zotero.Proxies.lastIPs = ipString; - // TODO IPv6 - var domains = [Zotero.Proxies.DNS.reverseLookup(ip) for each(ip in ips) if(ip.indexOf(":") == -1)]; - + var notificationCallbacks = channel.notificationCallbacks; + Zotero.Proxies.DNS.getHostnames().then(function (hosts) { // if domains necessitate disabling, disable them - Zotero.Proxies.disabledByDomain = domains.join(",").indexOf(Zotero.Proxies.disableByDomain) != -1; - } + Zotero.Proxies.disabledByDomain = false; + for (var host of hosts) { + Zotero.Proxies.disabledByDomain = host.toLowerCase().indexOf(Zotero.Proxies.disableByDomain) != -1; + if (Zotero.Proxies.disabledByDomain) return; + } + _maybeRedirect(channel, notificationCallbacks, proxied); + }); + Zotero.Proxies.lastIPCheck = now; + return; } - Zotero.Proxies.lastIPCheck = now; if(Zotero.Proxies.disabledByDomain) return; } + _maybeRedirect(channel, channel.notificationCallbacks, proxied); + } + + function _maybeRedirect(channel, notificationCallbacks, proxied) { // try to find a corresponding browser object - var bw = _getBrowserAndWindow(channel.notificationCallbacks); + var bw = _getBrowserAndWindow(notificationCallbacks); if(!bw) return; var browser = bw[0]; var window = bw[1]; @@ -918,106 +921,19 @@ Zotero.Proxies.Detectors.Juniper = function(channel) { Zotero.Proxies.DNS = new function() { var _callbacks = []; - this.getIPs = function() { - var dns = Components.classes["@mozilla.org/network/dns-service;1"] - .getService(Components.interfaces.nsIDNSService); - myHostName = dns.myHostName; - try { - var record = dns.resolve(myHostName, null); - } catch(e) { - return []; - } - - // get IPs - var ips = []; - while(record.hasMore()) { - ips.push(record.getNextAddrAsString()); - } - - return ips; - } - - this.reverseLookup = function(ip) { - Zotero.debug("Proxies: Performing reverse lookup for IP "+ip); - - // build DNS query - var bytes = Zotero.randomString(2)+"\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"; - - var ipParts = ip.split("."); - ipParts.reverse(); - for each(var ipPart in ipParts) { - bytes += String.fromCharCode(ipPart.length); - bytes += ipPart; - } - for each(var subdomain in ["in-addr", "arpa"]) { - bytes += String.fromCharCode(subdomain.length); - bytes += subdomain; - } - bytes += "\x00\x00\x0c\x00\x01"; - - var sts = Components.classes["@mozilla.org/network/socket-transport-service;1"] - .getService(Components.interfaces.nsISocketTransportService); - var transport = sts.createTransport(["udp"], 1, "8.8.8.8", 53, null); - var rawinStream = transport.openInputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, null, null); - var rawoutStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, null, null); - - var outStream = Components.classes["@mozilla.org/binaryoutputstream;1"] - .createInstance(Components.interfaces.nsIBinaryOutputStream); - outStream.setOutputStream(rawoutStream); - outStream.writeBytes(bytes, bytes.length); - outStream.close(); - - Zotero.debug("Proxies: Sent reverse lookup request"); - - var inStream = Components.classes["@mozilla.org/binaryinputstream;1"] - .createInstance(Components.interfaces.nsIBinaryInputStream); - var sinStream = Components.classes["@mozilla.org/scriptableinputstream;1"] - .createInstance(Components.interfaces.nsIScriptableInputStream); - inStream.setInputStream(rawinStream); - sinStream.init(rawinStream); - - var stuff = inStream.read32(); - var qdCount = inStream.read16(); - var anCount = inStream.read16(); - var nsCount = inStream.read16(); - var arCount = inStream.read16(); - - // read queries back out - for(var i=0; i rdLength) break; - var len = inStream.read8(); - bc += len; - if(bc > rdLength) break; - domain.push(sinStream.read(len)); - } - domain.pop(); - } - - domain = domain.join(".").toLowerCase(); - Zotero.debug("Proxies: "+ip+" PTR "+domain); - - inStream.close(); - return domain; + this.getHostnames = function() { + var deferred = Q.defer(); + var worker = new ChromeWorker("chrome://zotero/content/xpcom/dns_worker.js"); + Zotero.debug("Proxies.DNS: Performing reverse lookup"); + worker.onmessage = function(e) { + Zotero.debug("Proxies.DNS: Got hostnames "+e.data); + deferred.resolve(e.data); + }; + worker.onerror = function(e) { + Zotero.debug("Proxies.DNS: Reverse lookup failed"); + deferred.reject(e.message); + }; + worker.postMessage(Zotero.isWin ? "win" : Zotero.isMac ? "mac" : Zotero.isLinux ? "linux" : "unix"); + return deferred.promise; } }; \ No newline at end of file From 2c5bfd5d5a61f7ffac30e447ace38bccbc9fb296 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Mon, 13 Jul 2015 19:28:02 -0400 Subject: [PATCH 2/2] Disable disabling by reverse lookup on non-Mac/Windows/Linux If this is the case, it's probably a BSD derivative, but I can't guarantee the code won't segfault. --- chrome/content/zotero/xpcom/proxy.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chrome/content/zotero/xpcom/proxy.js b/chrome/content/zotero/xpcom/proxy.js index f2fa2e122..ab74dc606 100644 --- a/chrome/content/zotero/xpcom/proxy.js +++ b/chrome/content/zotero/xpcom/proxy.js @@ -175,6 +175,8 @@ Zotero.Proxies = new function() { if (Zotero.Proxies.disabledByDomain) return; } _maybeRedirect(channel, notificationCallbacks, proxied); + }, function(e) { + _maybeRedirect(channel, notificationCallbacks, proxied); }); Zotero.Proxies.lastIPCheck = now; return; @@ -922,6 +924,7 @@ Zotero.Proxies.DNS = new function() { var _callbacks = []; this.getHostnames = function() { + if (!Zotero.isWin && !Zotero.isMac && !Zotero.isLinux) return Q([]); var deferred = Q.defer(); var worker = new ChromeWorker("chrome://zotero/content/xpcom/dns_worker.js"); Zotero.debug("Proxies.DNS: Performing reverse lookup");