diff options
-rw-r--r-- | README.md | 2 | ||||
-rwxr-xr-x | apt-panopticon.lua | 105 |
2 files changed, 76 insertions, 31 deletions
@@ -31,12 +31,12 @@ in someplace like `/usr/local/bin` and make sure it is executable. | |||
31 | It should run on any recent Linux, you'll need to have the following | 31 | It should run on any recent Linux, you'll need to have the following |
32 | installed - | 32 | installed - |
33 | 33 | ||
34 | * curl | ||
34 | * dig, part of BIND. On Debian based systems it'll be in the dnsutils package. | 35 | * dig, part of BIND. On Debian based systems it'll be in the dnsutils package. |
35 | * flock, on Debian based systems it'll be in the util-linux package. | 36 | * flock, on Debian based systems it'll be in the util-linux package. |
36 | * ionice, on Debian based systems it'll be in the util-linux package. | 37 | * ionice, on Debian based systems it'll be in the util-linux package. |
37 | * luajit | 38 | * luajit |
38 | * LuaSocket, on Debian based systems it'll be in the lua-socket package. | 39 | * LuaSocket, on Debian based systems it'll be in the lua-socket package. |
39 | * LuaSec, on Debian based systems it'll be in the lua-sec package. | ||
40 | * wget | 40 | * wget |
41 | 41 | ||
42 | 42 | ||
diff --git a/apt-panopticon.lua b/apt-panopticon.lua index 227bec1..c536990 100755 --- a/apt-panopticon.lua +++ b/apt-panopticon.lua | |||
@@ -107,7 +107,6 @@ local logFile | |||
107 | local socket = require 'socket' | 107 | local socket = require 'socket' |
108 | local ftp = require 'socket.ftp' | 108 | local ftp = require 'socket.ftp' |
109 | local http = require 'socket.http' | 109 | local http = require 'socket.http' |
110 | local https = require 'ssl.https' -- See https://github.com/brunoos/luasec/wiki/LuaSec-0.6 for docs. | ||
111 | local url = require 'socket.url' | 110 | local url = require 'socket.url' |
112 | 111 | ||
113 | 112 | ||
@@ -286,42 +285,87 @@ checkHEAD = function (host, URL, r, retry) | |||
286 | if not testing(PU.scheme, host) then D("Not testing " .. PU.scheme .. " " .. host .. " -> " .. URL); return end | 285 | if not testing(PU.scheme, host) then D("Not testing " .. PU.scheme .. " " .. host .. " -> " .. URL); return end |
287 | -- TODO - Perhaps we should try it anyway, and mark it as a warning if it DOES work? | 286 | -- TODO - Perhaps we should try it anyway, and mark it as a warning if it DOES work? |
288 | if "https" == PU.scheme and options.roundRobin.value == host then D("Not testing " .. PU.scheme .. " " .. host .. " -> " .. URL .. " mirrors shouldn't have the correct cert."); return end | 287 | if "https" == PU.scheme and options.roundRobin.value == host then D("Not testing " .. PU.scheme .. " " .. host .. " -> " .. URL .. " mirrors shouldn't have the correct cert."); return end |
289 | local hd = {} | 288 | |
290 | if pu.host ~= PU.host then hd = {Host = host} end | 289 | --[[ Using curl command line - |
291 | local htp = http; | 290 | -I - HEAD |
292 | if PU.scheme == "https" then htp = https end | 291 | --connect-to IP - connect to IP, but use SNI from URL. |
293 | -- NOTE - the docs for lua-sec say that redirect isn't supported is version 0.6, no idea if that means it ignores redirections like we want. | 292 | -header "" - add extra headers. |
294 | -- TODO - find out! | 293 | -L - DO follow redirects. |
295 | -- The protocol and options are lua-sec specific arguments. | 294 | --max-redirs n - set maximum redirects, default is 50, -1 = unlimited. |
296 | local p, c, h, s = htp.request{method = "HEAD", redirect = false, url = URL, headers = hd, protocol = "any", options = "all"} | 295 | --retry n - maximum retries, default is 0, no retries. |
297 | if nil == s then s = "" end | 296 | -o file - write to file instead of stdout. |
298 | if nil == p then | 297 | --path-as-is - https://curl.haxx.se/libcurl/c/CURLOPT_PATH_AS_IS.html might be useful for URL_sanity. |
299 | E(" " .. c .. " " .. s .. "! " .. check .. " " .. host .. " -> " .. URL, PU.scheme, "", host) | 298 | -s silent - don't output progress or error messages. |
300 | -- So far the only errors are "timeout", "Network is unreachable", and "closed", and I suspect "closed" is due to saturating my bandwidth. | 299 | --connect-timeout n - timeout in seconds. |
301 | if "timeout" == c then timeouts = timeouts + 1 end | 300 | Should return with error code 28 on a timeout? |
302 | if ("closed" == c) or ("Network is unreachable" == c) or ("timeout" == c) then checkHEAD(host, URL, r, retry + 1, timeouts) end | 301 | -D file - write the received headers to a file. This includes the status code and string. |
303 | else | 302 | ]] |
304 | if ("4" == tostring(c):sub(1, 1)) or ("5" == tostring(c):sub(1, 1)) then | 303 | local fname = host .. "_" .. PU.host .. "_" .. PU.path:gsub("/", "_") .. ".txt" |
305 | E(" " .. c .. " " .. s .. ". " .. check .. " " .. host .. " -> " .. URL, PU.scheme, "", host) | 304 | local hdr = "" |
306 | else | 305 | local IP = "" |
307 | I(" " .. c .. " " .. s .. ". " .. check .. " " .. host .. " -> " .. URL) | 306 | if pu.host ~= PU.host then |
308 | timeouts = timeouts - 1 -- Backoff the timeouts count if we managed to get through. | 307 | if "http" == PU.scheme then |
308 | hdr = '-H "Host: ' .. host .. '"' | ||
309 | end | 309 | end |
310 | l = h.location | 310 | IP = '--connect-to ' .. PU.host |
311 | if nil ~= l then | 311 | end |
312 | pu = url.parse(l, defaultURL) | 312 | local cmd = 'curl -I --retry 0 -s --path-as-is --connect-timeout 30 --max-redirs 0 ' .. IP .. ' ' .. '-o /dev/null -D results/"HEADERS_' .. fname .. '" ' .. |
313 | hdr .. ' -w "#%{http_code} %{ssl_verify_result} %{url_effective}\\n" ' .. PU.scheme .. '://' .. host .. PU.path .. ' >>results/"STATUS_' .. fname .. '"' | ||
314 | local rslt, status = execute(cmd) | ||
315 | os.execute("sleep 2") | ||
316 | os.execute('cat results/"HEADERS_' .. fname .. '" >>results/"STATUS_' .. fname .. '"; rm results/"HEADERS_' .. fname .. '"') | ||
317 | if "exit" == rslt then | ||
318 | if 28 == status then | ||
319 | E(" TIMEOUT!", PU.scheme, "", host) | ||
320 | timeouts = timeouts + 1 | ||
321 | checkHEAD(host, URL, r, retry + 1, timeouts) | ||
322 | return | ||
323 | elseif 0 ~= status then | ||
324 | E(" The curl command return an error code of " .. status .. ", consult the curl manual for what this means.", PU.scheme, "", host) | ||
325 | checkHEAD(host, URL, r, retry + 1, timeouts) | ||
326 | return | ||
327 | end | ||
328 | elseif "signal" == rslt then | ||
329 | E(" The curl command was interupted by signal " .. status) | ||
330 | return | ||
331 | end | ||
332 | local rfile, e = io.open("results/STATUS_" .. fname, "r") | ||
333 | local code = "000" | ||
334 | local cstr = "" | ||
335 | local location = nil | ||
336 | if nil == rfile then W("opening results/STATUS_" .. fname .. " file - " .. e) else | ||
337 | for line in rfile:lines("*l") do | ||
338 | if "#" == line:sub(1, 1) then | ||
339 | code = line:sub(2, 4) | ||
340 | if ("https" == PU.scheme) and ("0" ~= line:sub(6, 6)) then E(" The certificate is invalid.", PU.scheme, "https", host) end | ||
341 | elseif "http" == line:sub(1, 4):lower() then | ||
342 | -- -2 coz the headers file gets a \r at the end. | ||
343 | cstr = line:sub(14, -2) | ||
344 | elseif "location" == line:sub(1, 8):lower() then | ||
345 | location = line:sub(11, -2) | ||
346 | end | ||
347 | end | ||
348 | end | ||
349 | os.execute('rm results/"STATUS_' .. fname .. '"') | ||
350 | if ("4" == tostring(code):sub(1, 1)) or ("5" == tostring(code):sub(1, 1)) then | ||
351 | E(" " .. code .. " " .. cstr .. ". " .. check .. " " .. host .. " -> " .. URL, PU.scheme, "", host) | ||
352 | else | ||
353 | I(" " .. code .. " " .. cstr .. ". " .. check .. " " .. host .. " -> " .. URL) | ||
354 | timeouts = timeouts - 1 -- Backoff the timeouts count if we managed to get through. | ||
355 | if nil ~= location then | ||
356 | pu = url.parse(location, defaultURL) | ||
313 | if (pu.scheme ~= PU.scheme) then | 357 | if (pu.scheme ~= PU.scheme) then |
314 | if testing("Protocol") then W(" protocol changed during redirect! " .. check .. " " .. host .. " -> " .. URL .. " -> " .. l, PU.scheme, "Protocol", host) end | 358 | if testing("Protocol") then W(" protocol changed during redirect! " .. check .. " " .. host .. " -> " .. URL .. " -> " .. location, PU.scheme, "Protocol", host) end |
315 | if (pu.host == host) and pu.path == PU.path then D("Not testing protocol change " .. URL .. " -> " .. l); return end | 359 | if (pu.host == host) and pu.path == PU.path then D("Not testing protocol change " .. URL .. " -> " .. location); return end |
316 | end | 360 | end |
317 | 361 | ||
318 | if l == URL then | 362 | if location == URL then |
319 | E(" redirect loop! " .. check .. " " .. host .. " -> " .. URL, PU.scheme, "", host) | 363 | E(" redirect loop! " .. check .. " " .. host .. " -> " .. URL, PU.scheme, "", host) |
320 | elseif nil == pu.host then | 364 | elseif nil == pu.host then |
321 | I(" relative redirect. " .. check .. " " .. host .. " -> " .. URL .. " -> " .. l) | 365 | I(" relative redirect. " .. check .. " " .. host .. " -> " .. URL .. " -> " .. location) |
322 | checkHEAD(host, PU.scheme .. "://" .. PU.host .. l, r + 1) | 366 | checkHEAD(host, PU.scheme .. "://" .. PU.host .. location, r + 1) |
323 | elseif (PU.host == pu.host) or (host == pu.host) then | 367 | elseif (PU.host == pu.host) or (host == pu.host) then |
324 | checkHEAD(pu.host, l, r + 1) | 368 | checkHEAD(pu.host, location, r + 1) |
325 | else | 369 | else |
326 | --[[ The hard part here is that we end up throwing ALL of the test files at the redirected location. | 370 | --[[ The hard part here is that we end up throwing ALL of the test files at the redirected location. |
327 | Not good for deb.debian.org, which we should only be throwing .debs at. | 371 | Not good for deb.debian.org, which we should only be throwing .debs at. |
@@ -448,6 +492,7 @@ local getMirrors = function () | |||
448 | I("getting mirrors.") | 492 | I("getting mirrors.") |
449 | local p, c, h = http.request(URL) | 493 | local p, c, h = http.request(URL) |
450 | if nil == p then E(c .. " fetching " .. URL) else | 494 | if nil == p then E(c .. " fetching " .. URL) else |
495 | |||
451 | for l in p:gmatch("\n*([^\n]+)\n*") do | 496 | for l in p:gmatch("\n*([^\n]+)\n*") do |
452 | local t, d = l:match("(%a*):%s*(.*)") | 497 | local t, d = l:match("(%a*):%s*(.*)") |
453 | d = string.lower(d) | 498 | d = string.lower(d) |