Rewrite to Python.
This commit is contained in:
parent
172dafcf4a
commit
bde616fc60
29
ansi.py
Normal file
29
ansi.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Listing of ANSI colors plus additional Windows support."""
|
||||
|
||||
import platform
|
||||
|
||||
# Needed to make ANSI escape sequences work in Windows
|
||||
SYSTEM = platform.system()
|
||||
if SYSTEM == "Windows":
|
||||
try:
|
||||
import colorama
|
||||
colorama.init()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
UP_DEL = u"\u001b[F\u001b[K"
|
||||
BLACK = u"\u001b[0;30m"
|
||||
RED_DARK = u"\u001b[0;31m"
|
||||
GREEN_DARK = u"\u001b[0;32m"
|
||||
YELLOW_DARK = u"\u001b[0;33m"
|
||||
CYAN_DARK = u"\u001b[0;36m"
|
||||
SILVER = u"\u001b[0;37m"
|
||||
GREY = u"\u001b[1;30m"
|
||||
RED = u"\u001b[1;31m"
|
||||
GREEN = u"\u001b[1;32m"
|
||||
YELLOW = u"\u001b[1;33m"
|
||||
CYAN = u"\u001b[1;36m"
|
||||
WHITE = u"\u001b[1;37m"
|
||||
RESET = u"\u001b[0m"
|
279
apt-urlcheck.php
279
apt-urlcheck.php
@ -1,279 +0,0 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
class APTChecker
|
||||
{
|
||||
private $whitelist = array();
|
||||
private $aptlists = array();
|
||||
private $codenames_old = array( 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid', 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy', 'trusty', 'utopic', 'vivid', 'wily' );
|
||||
private $codenames = array( 'devel', 'xenial', 'yakkety', 'zesty', 'artful', 'bionic', 'debian', 'squeeze', 'stable', 'unstable', 'beta' );
|
||||
private $codename;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->codename = exec('lsb_release -cs');
|
||||
|
||||
$this->addSourceLists( '/etc/apt/sources.list' );
|
||||
$this->addSourceLists( '/etc/apt/sources.list.d/*.list' );
|
||||
}
|
||||
|
||||
public function addSourceLists( $filemask )
|
||||
{
|
||||
$this->aptlists = array_merge( $this->aptlists, glob( $filemask ) );
|
||||
}
|
||||
|
||||
public function getSourceLists()
|
||||
{
|
||||
return $this->aptlists;
|
||||
}
|
||||
|
||||
public function addWhitelist( $codename )
|
||||
{
|
||||
if ( !is_array( $codename ) ) $codename = array( $codename );
|
||||
foreach ( $codename as $cn ) {
|
||||
$this->whitelist[] = $cn;
|
||||
}
|
||||
}
|
||||
|
||||
public function isRoot()
|
||||
{
|
||||
return (posix_getuid() == 0);
|
||||
}
|
||||
|
||||
public function getCodename()
|
||||
{
|
||||
return $this->codename;
|
||||
}
|
||||
|
||||
public function parseLists()
|
||||
{
|
||||
$debs = array();
|
||||
foreach ( $this->getSourceLists() as $path ) {
|
||||
$fc = file($path, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($fc as $i=>$fline) {
|
||||
if ((substr($fline, 0, 4) == 'deb ') || (substr($fline, 0, 8) == 'deb-src ')) {
|
||||
$debs[] = array(
|
||||
'file' => $path,
|
||||
'line' => $i+1,
|
||||
'data' => $fline,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $debs;
|
||||
}
|
||||
|
||||
public function parseDebLine( $line )
|
||||
{
|
||||
$parts = explode(' ', $line);
|
||||
if ( $parts[1]{0} == '[' ) {
|
||||
$result = array(
|
||||
'type' => $parts[0],
|
||||
'attributes' => $parts[1],
|
||||
'url' => $parts[2],
|
||||
'distr' => $parts[3],
|
||||
'components' => array_slice($parts, 4),
|
||||
);
|
||||
} else {
|
||||
$result = array(
|
||||
'type' => $parts[0],
|
||||
'attributes' => '',
|
||||
'url' => $parts[1],
|
||||
'distr' => $parts[2],
|
||||
'components' => array_slice($parts, 3),
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function tryFetch( $url ) {
|
||||
$answer = array();
|
||||
stream_context_set_default( array(
|
||||
'http' => array(
|
||||
'method' => 'HEAD',
|
||||
'timeout' => 5.0,
|
||||
),
|
||||
) );
|
||||
$result = get_headers( $url, 1 );
|
||||
$status_line = $result[0];
|
||||
if ( is_array( $status_line ) ) {
|
||||
$status_line = end( $status_line );
|
||||
}
|
||||
$status = substr( $status_line, 9, 3 );
|
||||
|
||||
stream_context_set_default( array(
|
||||
'http' => array(
|
||||
'method' => 'GET',
|
||||
),
|
||||
) );
|
||||
|
||||
return ( $status == '200' );
|
||||
}
|
||||
|
||||
private function tryGetDirectoryListing( $url )
|
||||
{
|
||||
$all_known_codenames = array_merge($this->codenames_old, $this->codenames);
|
||||
$list = @file_get_contents( $url );
|
||||
if ( $list === false ) return false;
|
||||
preg_match_all('/<a .*?href=[\'"]?([^\'"]+)[\'"]?.*?>/i', $list, $matches);
|
||||
#print_r($matches);
|
||||
$result = array();
|
||||
foreach ($matches[1] as $match) {
|
||||
if ($match{0} != '?' && $match{0} != '/' && substr($match, -1) == '/' && $match != '../' && substr($match, 0, 4) != 'http') {
|
||||
$result[] = substr($match, 0, -1);
|
||||
} elseif (in_array($match, $all_known_codenames) || in_array(substr($match, 0, -1), $all_known_codenames)) {
|
||||
$result[] = $match;
|
||||
}
|
||||
}
|
||||
#print_r($result);
|
||||
return array_unique( $result );
|
||||
}
|
||||
|
||||
private function tryReleases( $baseurl, $additional = array() )
|
||||
{
|
||||
if ( !is_array( $additional ) ) $additional = array( $additional );
|
||||
|
||||
$result = array();
|
||||
foreach ( array_unique( array_merge( $this->codenames, $additional ) ) as $codename ) {
|
||||
foreach ( array( 'InRelease', 'Release', 'Release.gpg' ) as $filename ) {
|
||||
$try_url = $baseurl . '/dists/' . $codename . '/' . $filename;
|
||||
$exists = $this->tryFetch( $try_url );
|
||||
if ( $exists !== false ) {
|
||||
$result[] = $codename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getServerInfo( $info )
|
||||
{
|
||||
$result = array();
|
||||
$result['method'] = 'dirlist';
|
||||
$dirlist = $this->tryGetDirectoryListing( $info['url'] . '/dists' );
|
||||
if ( $dirlist === false ) {
|
||||
// Damn!
|
||||
$dirlist = $this->tryReleases( $info['url'], $info['distr'] );
|
||||
$result['method'] = 'bruteforce';
|
||||
}
|
||||
$result['dists'] = $dirlist;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function analyzeDebs( $debs = null, $progress = null )
|
||||
{
|
||||
if (is_null($debs)) $debs = $this->parseLists();
|
||||
$result = array();
|
||||
foreach ( $debs as $deb ) {
|
||||
$info = $this->parseDebLine( $deb['data'] );
|
||||
if ( substr( $info['distr'], 0, strlen( $this->codename ) ) != $this->codename && !in_array( $info['distr'], $this->whitelist ) ) {
|
||||
$serverinfo = $this->getServerInfo( $info );
|
||||
$result[] = array(
|
||||
'deb' => $deb,
|
||||
'info' => $info,
|
||||
'server' => $serverinfo,
|
||||
);
|
||||
}
|
||||
if ($this->isRoot()) $this->getKeyId($info['url'], $info['distr']); // cache key-id
|
||||
if (!is_null($progress)) call_user_func($progress);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getKeyId( $url, $dist )
|
||||
{
|
||||
global $keycache;
|
||||
if (!isset($keycache)) $keycache = array();
|
||||
if (isset($keycache[$url][$dist])) return $keycache[$url][$dist];
|
||||
$sigfile = @file_get_contents($url.'/dists/'.$dist.'/Release.gpg');
|
||||
if ($sigfile === false) {
|
||||
echo 'No signature found.' . PHP_EOL;
|
||||
$keycache[$url][$dist] = false;
|
||||
return false;
|
||||
}
|
||||
exec('echo "' . $sigfile . '" | gpg --batch --verify - /etc/passwd 2>&1', $output, $retval);
|
||||
$output = implode(PHP_EOL, $output);
|
||||
preg_match('/gpg: Signature made (.*) using (.*) key ID (.*)\r?\n/m', $output, $matches);
|
||||
$result = array(
|
||||
'date' => $matches[1],
|
||||
'type' => $matches[2],
|
||||
'id' => $matches[3],
|
||||
'desc' => $matches[4],
|
||||
);
|
||||
echo 'Repo uses key id ' . $result['id'] . PHP_EOL;
|
||||
if (empty($result['id'])) {
|
||||
echo 'DEBUG: ' . $output . PHP_EOL;
|
||||
}
|
||||
$keycache[$url][$dist] = $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function outputResults( $debinfo ) {
|
||||
$all_codenames = array_merge( $this->codenames_old, $this->codenames );
|
||||
foreach ( $debinfo as $di ) {
|
||||
echo 'Mismatching distribution "' . $di['info']['distr'] . '" in ' . $di['deb']['file'] . ':' . $di['deb']['line'] . PHP_EOL;
|
||||
$better = array();
|
||||
$current = array_search( $di['info']['distr'], $all_codenames );
|
||||
foreach ( $di['server']['dists'] as $dist_avail ) {
|
||||
$where = array_search( $dist_avail, $all_codenames );
|
||||
if ( $where === false || $where > $current ) {
|
||||
$better[] = $dist_avail;
|
||||
}
|
||||
}
|
||||
//echo 'Available: ' . implode( ', ', $di['server']['dists'] ) . PHP_EOL;
|
||||
if ( count( $better ) > 0 ) echo 'Possibly better matches: ' . implode( ', ', $better ) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ac = new APTChecker();
|
||||
$ac->addWhitelist( array( 'stable', 'unstable', 'beta' ) );
|
||||
|
||||
echo 'System: ' . $ac->getCodename() . PHP_EOL;
|
||||
|
||||
echo 'Running as user id ' . posix_getuid() . PHP_EOL;
|
||||
|
||||
echo 'Parsing lists ... ';
|
||||
$debs = $ac->parseLists();
|
||||
echo count($debs) . ' entries found.' . PHP_EOL;
|
||||
|
||||
if ( $ac->isRoot() ) {
|
||||
echo 'Fetching key ids of keyring ... ';
|
||||
exec('apt-key list', $output, $retval);
|
||||
$output = implode(PHP_EOL, $output);
|
||||
preg_match_all('/pub.*\/([^\s]+).*\r?\n/m', $output, $matches);
|
||||
$allkeys = $matches[1];
|
||||
echo count($allkeys) . ' keys found.' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Not started as root. Will not check GPG keys.' . PHP_EOL;
|
||||
}
|
||||
|
||||
$debinfo = $ac->analyzeDebs( $debs, 'progressInd' );
|
||||
echo PHP_EOL;
|
||||
$ac->outputResults( $debinfo );
|
||||
|
||||
if ( $ac->isRoot() ) {
|
||||
foreach ( $keycache as $url=>$dists ) {
|
||||
foreach ( $dists as $dist=>$key ) {
|
||||
if ( !empty( $key['id'] ) && !in_array( $key['id'], $allkeys ) ) {
|
||||
echo 'Importing key ' . $key['id'] . ' ... ';
|
||||
passthru( 'apt-key adv --batch --recv-keys --keyserver keyserver.ubuntu.com ' . $key['id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo 'All done.';
|
||||
if ( !$ac->isRoot() ) echo ' (Run as root to import missing PPA keys.)';
|
||||
echo PHP_EOL;
|
||||
exit;
|
||||
|
||||
function progressInd() {
|
||||
echo '.';
|
||||
}
|
||||
|
||||
function checkCurrentDist($url) {
|
||||
$fullurl = 'http://linux.getdropbox.com/ubuntu/dists/jaunty/main/binary-i386/Packages.gz';
|
||||
}
|
||||
|
116
apt-urlcheck.py
Executable file
116
apt-urlcheck.py
Executable file
@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ansi
|
||||
import os.path
|
||||
import re
|
||||
import requests
|
||||
from aptsources.distro import get_distro
|
||||
from aptsources.sourceslist import SourcesList
|
||||
from typing import TypeVar
|
||||
|
||||
codenames_okay = ["devel", "stable", "unstable", "beta", "preview", "testing", "syncthing"]
|
||||
codenames_old = ["gutsy", "hardy", "intrepid", "jaunty", "karmic", "lucid", "maverick", "natty", "oneiric", "precise", "quantal", "raring", "saucy", "trusty", "utopic", "vivid", "wily"]
|
||||
codenames = ["jessie", "xenial", "yakkety", "zesty", "stretch", "artful", "bionic", "squeeze"]
|
||||
|
||||
codename = get_distro().codename
|
||||
print("This is {}.".format(ansi.YELLOW + codename + ansi.RESET))
|
||||
|
||||
print("Loading sources...", end="", flush=True)
|
||||
|
||||
valid_sources = 0
|
||||
outdated_sources = 0
|
||||
check_sources = []
|
||||
for source in SourcesList():
|
||||
if source.disabled or source.line=="\n":
|
||||
continue
|
||||
valid_sources += 1
|
||||
#print(".", end="", flush=True)
|
||||
if codename in source.dist or source.dist in codenames_okay:
|
||||
continue
|
||||
outdated_sources += 1
|
||||
check_sources.append(source)
|
||||
#print("{}: {}".format(source.file, source.line.strip()))
|
||||
|
||||
check_sources = sorted(check_sources, key=lambda x: x.file)
|
||||
|
||||
print(" OK")
|
||||
print("Found {} sources with {} possibly outdated.".format(valid_sources, outdated_sources))
|
||||
|
||||
|
||||
BorL = TypeVar("BorL", bool, list)
|
||||
|
||||
fetch_cache = {}
|
||||
|
||||
def try_fetch_dirlisting(url: str) -> BorL:
|
||||
global codenames_okay, codenames_old, codenames, fetch_cache
|
||||
if url in fetch_cache:
|
||||
return fetch_cache[url]
|
||||
all_known_codenames = codenames_old + codenames
|
||||
result = requests.get(url)
|
||||
if result.status_code != 200:
|
||||
fetch_cache[url] = False
|
||||
return False
|
||||
matches = re.findall(r"<a .*?href=['\"]?([^'\"]+)['\"]?.*?>", result.text)
|
||||
valid_matches = []
|
||||
for match in matches:
|
||||
if match[0] != "?" and match[0] != "/" and match[-1:] == "/" and match != "../" and match[0:4] != "http":
|
||||
valid_matches.append(match[0:-1])
|
||||
elif match in all_known_codenames or match[0:-1] in all_known_codenames:
|
||||
valid_matches.append(match)
|
||||
fetch_cache[url] = valid_matches
|
||||
return fetch_cache[url]
|
||||
|
||||
def mutate_codename(current_codename: str, new_codename: str) -> str:
|
||||
global codenames_okay, codenames_old, codenames
|
||||
all_codenames = codenames_old + codenames
|
||||
for cn in all_codenames:
|
||||
if cn in current_codename:
|
||||
return current_codename.replace(cn, new_codename)
|
||||
return new_codename
|
||||
|
||||
probe_cache = {}
|
||||
|
||||
def try_url_probing(url: str, current_codename: str) -> list:
|
||||
global codenames_okay, codenames_old, codenames, probe_cache
|
||||
cache_key = url + "|" + current_codename
|
||||
if cache_key in probe_cache:
|
||||
return probe_cache[cache_key]
|
||||
test_set = codenames + codenames_okay
|
||||
valid_matches = []
|
||||
for codename in test_set:
|
||||
for filename in ["InRelease", "Release", "Release.gpg"]:
|
||||
mcodename = mutate_codename(current_codename, codename)
|
||||
try_url = "{}/{}/{}".format(url, mcodename, filename)
|
||||
print(".", end="", flush=True)
|
||||
result = requests.get(try_url)
|
||||
if result.status_code == 200:
|
||||
valid_matches.append(mcodename)
|
||||
break
|
||||
probe_cache[cache_key] = valid_matches
|
||||
return probe_cache[cache_key]
|
||||
|
||||
def filter_better_matches(found_codenames: list, current_codename: str) -> list:
|
||||
global codenames, codenames_okay, codenames_old
|
||||
better_codenames = []
|
||||
for cn in found_codenames:
|
||||
if cn > current_codename:
|
||||
better_codenames.append(cn)
|
||||
elif cn in codenames_okay:
|
||||
better_codenames.append(cn)
|
||||
return better_codenames
|
||||
|
||||
for src in check_sources:
|
||||
print("{}: Outdated codename: {}".format(ansi.CYAN + os.path.basename(src.file) + ansi.RESET, ansi.RED + src.dist + ansi.RESET))
|
||||
test_url = src.uri + "/dists"
|
||||
more_options = try_fetch_dirlisting(test_url)
|
||||
if not more_options:
|
||||
print("Listing failed. Probing", end="", flush=True)
|
||||
more_options = try_url_probing(test_url, src.dist)
|
||||
print(" OK")
|
||||
print(ansi.UP_DEL, end="")
|
||||
better_options = filter_better_matches(more_options, src.dist)
|
||||
if better_options:
|
||||
print("Possibly better options: {}".format(ansi.GREEN + (ansi.RESET + ", " + ansi.GREEN).join(better_options) + ansi.RESET))
|
||||
else:
|
||||
print(ansi.SILVER + "No better match(es) found at the moment." + ansi.RESET)
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# Install via aptitude, pip doesn't work!
|
||||
python-apt
|
||||
python-distutils-extra
|
Reference in New Issue
Block a user