diff --git a/.travis.yml b/.travis.yml index fdb7a5b..801ca94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ -language: bash +language: rust before_install: - wget -c https://goo.gl/ZzKHFv -O - | tar -xvJ -C /tmp/ - PATH="/tmp/shellcheck-latest:$PATH" -script: shellcheck $(grep -rl '^#!.*[ /]bash$' .) +script: | + shellcheck $(grep -rl '^#!.*[ /]bash$' .) + (cd trust-check && cargo test) diff --git a/README.md b/README.md index 5ac733f..a31eec0 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ An Arch Linux AUR tool for managing an auto-updating local 'aurto' package repos - Automatic on startup & hourly update of aur packages in the ***aurto*** repo. - Automatic daily update of `*-git` packages in the ***aurto*** repo. - Uses _makechrootpkg_ to build packages isolated from the main system. +- Automatic removal of packages no longer in the AUR from the ***aurto*** repo. +- Automatic removal of packages with unknown/distrusted maintainers from the ***aurto*** repo. # Install From a plain Arch install, first install **aurutils** from the aur (skip if already installed). @@ -55,14 +57,38 @@ Add a directory full of built packages to the ***aurto*** repo aurto addpkg $(find /path/to/packages/*pkg.tar*) ``` +Show repo-less installed packages, these may have not been added to ***aurto*** yet or may have been automatically dropped from ***aurto*** because of maintainer change or removal from the AUR. +```sh +pacman -Qm +``` + Rebuild all orphans packages into the ***aurto*** repo ```sh aurto add $(pacman -Qqm) ``` +# Maintainer Trust +**aurto** uses a system of maintainer trust for limited security. On adding packages with unknown maintainers you'll be asked whether you want to trust these maintainers. +``` +$ aurto add spotify +aurto: Trust maintainer(s): AWhetter? [y/N] +``` +If not the package will _not_ be added to the ***aurto*** repo. + +If any ***aurto*** repo packages changes maintainer to an unknown maintainer they will be removed from the ***aurto*** repo on the next _update-aurto_ run. A warning will appear in the _update-aurto_ logs +``` +WARNING: Packages with unknown maintainers removed from aurto, ... +``` +If desired such packages can be re-added and the new maintainer added to the local trusted users. + +Local trusted users are stored in `/etc/aurto/trusted-users` initially populated with the [Arch Linux Trusted Users](https://wiki.archlinux.org/index.php/Trusted_Users#Active_Trusted_Users) & me. + +Clear `/etc/aurto/trusted-users` to trust no-one.
+Remove `/etc/aurto/trusted-users` to trust everyone. + # Limitations & Security -aurto automatically builds and regularly re-builds updated remote code from the aur. +**aurto** automatically builds and regularly re-builds updated remote code from the aur. Code is _built_ in a clean chroot, but presumably will eventually be installed to your system. -Only add aur packages from maintainers you trust. +Take care trusting maintainers. -aurto is for simple folk's simple needs. If it can't do what you want uninstall & use [aurutils](https://github.com/AladW/aurutils) directly. +If aurto can't do what you want use [aurutils](https://github.com/AladW/aurutils) directly. diff --git a/bin/aurto b/bin/aurto index bcebfcc..d43dd07 100755 --- a/bin/aurto +++ b/bin/aurto @@ -19,19 +19,63 @@ source "$lib_dir/shared-functions" function aurto_sync { sudo pacsync aurto >/dev/null; } +function check_new_package_trust { + local mistrust + local not_in_aur + local mistrusted_users + + if [ -f /etc/aurto/trusted-users ]; then + echo "aurto: Checking maintainer trust..." >&2 + else + echo "aurto: Checking maintainer trust... $(dim disabled)" >&2 + fi + + mistrust=$("$lib_dir"/trust-check "${@:1}") + if [ -z "$mistrust" ]; then + if [ -f /etc/aurto/trusted-users ]; then + rm_last_print + echo "aurto: Checking maintainer trust... $(green ✓)" >&2 + fi + else + not_in_aur=$(not_in_aur_packages "$mistrust") + if [ ! -z "$not_in_aur" ]; then + rm_last_print + echo "aurto: Package not in AUR: $(yellow "$not_in_aur")" >&2 + exit 1 + fi + + mistrusted_users=$(new_line_to_space_separated_unique "$(echo "$mistrust" | cut -d: -f2)") + rm_last_print + echo -n "aurto: Trust maintainer(s): $(bold "$mistrusted_users")? [y/N] " >&2 + read -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "aurto: Adding $(bold "$mistrusted_users") >> /etc/aurto/trusted-users" >&2 + # shellcheck disable=SC2001 + echo "$mistrusted_users" | sed -e 's/ /\n/g' >> /etc/aurto/trusted-users + else + exit 1 + fi + fi +} + if [ "$command" == "add" ] && [ -n "$arg1" ]; then + check_new_package_trust "${@:2}" aurto_sync echo "aurto: Running: $(dim aursync --no-view --no-confirm --chroot --repo=aurto) $(cyan "${*:2}")" >&2 "$lib_dir"/summerize-build aursync --no-view --no-confirm --chroot --repo=aurto "${@:2}" aurto_sync echo -e "aurto: To install run: $(green pacman -Syu) $(cyan "${*:2}")" >&2 + elif [ "$command" == "addpkg" ] && [ -n "$arg1" ]; then + check_new_package_trust "${@:2}" echo "aurto: Running: $(dim repo-add /var/cache/pacman/aurto/aurto.db.tar) $(cyan "${*:2}")" >&2 - repo-add /var/cache/pacman/aurto/aurto.db.tar "${@:2}" + "$lib_dir"/summerize-build repo-add /var/cache/pacman/aurto/aurto.db.tar "${@:2}" for pkg in "${@:2}"; do cp "$pkg" /var/cache/pacman/aurto/ done aurto_sync + elif [ "$command" == "remove" ] && [ -n "$arg1" ]; then removed="" for pkg in "${@:2}"; do @@ -47,6 +91,7 @@ elif [ "$command" == "remove" ] && [ -n "$arg1" ]; then echo -e "aurto: Removed $(cyan "$removed")" >&2 aurto_sync fi + else echo "$(bold aurto) v$version: simple management tool for the 'aurto' repository" echo " Usage: $(green aurto add)|$(green addpkg)|$(green remove) $(cyan PACKAGES...)" diff --git a/lib/aurto/default-trusted-users.txt b/lib/aurto/default-trusted-users.txt new file mode 100644 index 0000000..d8ade2e --- /dev/null +++ b/lib/aurto/default-trusted-users.txt @@ -0,0 +1,49 @@ +alad +alexheretic +alucryd +Ambrevar +anatolik +andrewSC +anthraxx +arcanis +arojas +Barthalion +BlackIkeEagle +Bluewind +City-busz +coderobe +ConnorBehan +lfleischer +eworm +Dragonlord +dvzrv +eschwartz +escondida +farseerfc +felixonmars +Foxboron +foxxx0 +giniu +grazzolini +heftig +jelly +jleclanche +jsteel +keenerd +Kyrias +Lordheavy +mtorromeo +Muflone +NicoHood +schivmeister +schuay +seblu +sergej +shibumi +stativ +svenstaro +tensor5 +wild +Xyne +xyproto +zorun diff --git a/lib/aurto/shared-functions b/lib/aurto/shared-functions index 59a528b..1b2a1e6 100644 --- a/lib/aurto/shared-functions +++ b/lib/aurto/shared-functions @@ -4,6 +4,7 @@ if test -t 1; then function green { echo -e "\\e[32m$*\\e[39m"; } function cyan { echo -e "\\e[36m$*\\e[39m"; } function red { echo -e "\\e[31m$*\\e[39m"; } + function yellow { echo -e "\\e[33m$*\\e[39m"; } function dim { echo -e "\\e[2m$*\\e[22m"; } function rm_last_print { printf "\\033[1A" # move cursor one line up @@ -14,6 +15,7 @@ else function green { bold "$@"; } function cyan { bold "$@"; } function red { bold "$@"; } + function yellow { bold "$@"; } function dim { bold "$@"; } function rm_last_print { return; } fi @@ -22,3 +24,29 @@ fi function last_pkg_modify { stat /var/cache/pacman/aurto/*.pkg.tar* -c '%Y' 2>/dev/null | sort | tail -n1 | tr -d '\n' } + +## Takes '\n' separated things and returns a ' ' separated unique sequence (no empties) +function new_line_to_space_separated_unique { + local space_sep="" + + while read -r line; do + if [ ! -z "$line" ] && [[ $space_sep != *" $line "* ]]; then + space_sep="$space_sep $line " + fi + done <<< "$1" + + echo "$space_sep" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -s ' ' +} + +## Works on trust-check output returning packages that are not/no longer in AUR +function not_in_aur_packages { + local packages="" + + while read -r line; do + if [ ! -z "$line" ] && [[ $line = *"::not-in-aur"* ]]; then + packages="$packages $(echo "$line" | cut -d':' -f1)" + fi + done <<< "$1" + + echo "$packages" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -s ' ' +} diff --git a/lib/aurto/update-aurto b/lib/aurto/update-aurto index 20d74de..54db641 100755 --- a/lib/aurto/update-aurto +++ b/lib/aurto/update-aurto @@ -20,6 +20,39 @@ fi pacsync aurto >/dev/null || true +## Check trust +## - remove packages no longer in the AUR +## - remove packages with maintainers lacking trust +if [ -f /etc/aurto/trusted-users ]; then + echo "aurto: Checking maintainer trust..." >&2 +else + echo "aurto: Checking maintainer trust... $(dim disabled)" >&2 +fi +# shellcheck disable=SC2046 +mistrust=$("$lib_dir"/trust-check $(pacman -Slq aurto)) +if [ -z "$mistrust" ]; then + if [ -f /etc/aurto/trusted-users ]; then + rm_last_print + echo "Checking maintainer trust... $(green ✓)" >&2 + fi +else + not_in_aur=$(not_in_aur_packages "$mistrust") + mistrusted_pkgs=$(new_line_to_space_separated_unique "$(echo "$mistrust" | grep -v '::' | cut -d: -f1)") + + if [ ! -z "$not_in_aur" ]; then + rm_last_print + # shellcheck disable=SC2086 + aurto remove $not_in_aur + echo "$(yellow WARNING:) Packages no longer in AUR removed from aurto: $(yellow "$not_in_aur")" >&2 + fi + if [ ! -z "$mistrusted_pkgs" ]; then + # shellcheck disable=SC2086 + aurto remove $mistrusted_pkgs + echo -n "$(yellow WARNING:) Packages with unknown maintainers removed from aurto, " >&2 + echo "re-add with: $(green aurto add) $(cyan "$mistrusted_pkgs")" >&2 + fi +fi + modify=$(last_pkg_modify) echo "Running: aursync --no-view --no-confirm --repo aurto --chroot --update" >&2 diff --git a/makefile b/makefile index 5f064c2..e91214b 100644 --- a/makefile +++ b/makefile @@ -3,12 +3,15 @@ PREFIX = /usr all: @rm -rf target + @(cd trust-check && cargo build --release && strip target/release/trust-check) + @install -D conf/aurto.pacman.conf target/etc/pacman.d/aurto @install -Dm440 conf/50_aurto_passwordless -t target/etc/sudoers.d @chmod 750 target/etc/sudoers.d @install -D bin/* -t target$(PREFIX)/bin @install -D lib/aurto/* -t target$(PREFIX)/lib/aurto + @install trust-check/target/release/trust-check -t target$(PREFIX)/lib/aurto @install -D timer/* -t target$(PREFIX)/lib/systemd/system @install -D completion/bash/aurto target$(PREFIX)/share/bash-completion/completions/aurto diff --git a/makelocalaur b/makelocalaur index 1cf5cda..8204e9a 100755 --- a/makelocalaur +++ b/makelocalaur @@ -2,8 +2,10 @@ set -eu script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +rm -rf "$script_dir"/*-999-*pkg* + cd "$script_dir"/.. -tar zcf aurto-git.tar.gz -C "$script_dir" . +tar zcf aurto-git.tar.gz --exclude='./target' --exclude='./trust-check/target' -C "$script_dir" . mv aurto-git.tar.gz "$script_dir"/aur cd "$script_dir"/aur diff --git a/makelocalaur.PKGBUILD b/makelocalaur.PKGBUILD index aca8abb..22943bb 100644 --- a/makelocalaur.PKGBUILD +++ b/makelocalaur.PKGBUILD @@ -11,9 +11,10 @@ depends=('aurutils<1.6.0' 'devtools' 'systemd' 'pacutils' - 'pacman-contrib') + 'pacman-contrib' + 'curl') optdepends=() -makedepends=() +makedepends=('cargo') install="aurto.install" source=("aurto-git.tar.gz") sha256sums=('eb94c0a2920ddea570621da7326f3d60c30401e8c42073b5b3ed3b1216c1ce4b') diff --git a/trust-check/.gitignore b/trust-check/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/trust-check/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/trust-check/Cargo.lock b/trust-check/Cargo.lock new file mode 100644 index 0000000..97fdd3e --- /dev/null +++ b/trust-check/Cargo.lock @@ -0,0 +1,177 @@ +[[package]] +name = "cc" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "curl" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "curl-sys 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curl-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "json" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libz-sys" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "socket2" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "trust-check" +version = "0.1.0" +dependencies = [ + "curl 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcpkg" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d" +"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" +"checksum curl 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf20bbe084f285f215eef2165feed70d6b75ba29cad24469badb853a4a287d0" +"checksum curl-sys 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71c63a540a9ee4e15e56c3ed9b11a2f121239b9f6d7b7fe30f616e048148df9a" +"checksum json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad0485404155f45cce53a40d4b2d6ac356418300daed05273d9e26f91c390be" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" +"checksum libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "87f737ad6cc6fd6eefe3d9dc5412f1573865bded441300904d2f42269e140f16" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6a27d108b29befe1822d40e2e22f85518dac59acbf7f30fdc532f48fd0a77" +"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" +"checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade" +"checksum socket2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ff606e0486e88f5fc6cfeb3966e434fb409abbc7a3ab495238f70a1ca97f789d" +"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/trust-check/Cargo.toml b/trust-check/Cargo.toml new file mode 100644 index 0000000..dc1ca55 --- /dev/null +++ b/trust-check/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "trust-check" +version = "0.1.0" +authors = ["Alex Butler "] + +[dependencies] +curl = "0.4" +json = "0.11" + +[profile.release] +lto = true diff --git a/trust-check/src/main.rs b/trust-check/src/main.rs new file mode 100644 index 0000000..1a196c1 --- /dev/null +++ b/trust-check/src/main.rs @@ -0,0 +1,178 @@ +//! Arch Linux binary to return input aur packages along with their +//! mistrusted maintainers. Local trusted users are stored in +//! /etc/aurto/trusted-users. +//! +//! Will output a line per package in PACKAGE_NAME:USERNAME format +//! for each mistrusted package. +//! +//! If package is not found in the AUR will output PACKAGE_NAME::not-in-aur + +extern crate curl; +extern crate json; + +use std::collections::HashSet; +use std::error::Error; +use std::path::Path; +use std::{env, fs, str}; + +type Res = Result>; + +const AURWEB_INFO: &str = "https://aur.archlinux.org/rpc/?v=5&type=info"; +const LOCAL_TRUST_PATH: &str = "/etc/aurto/trusted-users"; + +fn main() -> Res<()> { + let mut packages = vec![]; + for arg in env::args().skip(1) { + packages.push(translate_full_package(arg)?); + } + + if packages.is_empty() || packages.iter().any(|p| p.starts_with('-')) { + return print_help(); + } + + let trust_everyone = !Path::new(LOCAL_TRUST_PATH).is_file(); + + let trusted = if trust_everyone { + HashSet::new() + } else { + local_trusted_users()? + }; + + let (pkg_maintainers, not_in_aur) = package_maintainers(&packages)?; + + if !trust_everyone { + for pkg in pkg_maintainers + .into_iter() + .filter(|pkg| !trusted.contains(&pkg.maintainer.to_lowercase())) + { + println!("{}:{}", pkg.name, pkg.maintainer); + } + } + + for pkg in not_in_aur { + println!("{}::not-in-aur", pkg); + } + + Ok(()) +} + +/// normalises package names & full package archive names -> package names +fn translate_full_package(arg: String) -> Result { + if arg.contains(".pkg.") { + let archive_name = Path::new(&arg) + .file_name() + .and_then(|f| f.to_str()) + .ok_or_else(|| format!("Can't handle arg `{}`", arg))?; + let mut name_bits: Vec<_> = archive_name.split('-').rev().skip(3).collect(); + name_bits.reverse(); + Ok(name_bits.join("-")) + } + else { + Ok(arg) + } +} + +fn local_trusted_users() -> Res> { + Ok(fs::read_to_string(LOCAL_TRUST_PATH)? + .split('\n') + .map(|user| user.trim().to_lowercase()) + .filter(|user| !user.is_empty()) + .collect()) +} + +#[derive(Debug)] +struct MaintainedPackage { + name: String, + maintainer: String, +} + +fn package_maintainers>( + packages: &[T], +) -> Res<(Vec, Vec)> { + let url = { + let mut url = AURWEB_INFO.to_owned(); + for pkg in packages { + url = url + "&arg[]=" + valid_arch_package_name(pkg.as_ref())?; + } + url + }; + + let mut buf = Vec::new(); + { + let mut handle = curl::easy::Easy::new(); + handle.url(&url)?; + let mut transfer = handle.transfer(); + transfer.write_function(|data| { + buf.extend_from_slice(data); + Ok(data.len()) + })?; + transfer.perform()?; + } + + let mut json = json::parse(str::from_utf8(&buf)?)?; + + let not_in_aur: Vec<_> = { + let in_aur: HashSet<_> = json["results"] + .members() + .filter_map(|info| info["Name"].as_str().map(|s| s.to_lowercase())) + .collect(); + packages + .iter() + .map(|p| p.as_ref().to_lowercase()) + .filter(|pkg| !in_aur.contains(pkg)) + .collect() + }; + + let mut maintained_pkgs = vec![]; + for info in json["results"].members_mut() { + let name = info["Name"].take_string(); + let maintainer = info["Maintainer"].take_string(); + if let (Some(name), Some(maintainer)) = (name, maintainer) { + maintained_pkgs.push(MaintainedPackage { name, maintainer }); + } + } + + Ok((maintained_pkgs, not_in_aur)) +} + +fn valid_arch_package_name(name: &str) -> Result<&str, String> { + fn valid_char(c: char) -> bool { + // https://wiki.archlinux.org/index.php/Arch_packaging_standards#Package_naming + // enforced to ensure a valid url request later + c.is_alphanumeric() || c == '@' || c == '.' || c == '_' || c == '+' || c == '-' + } + + if !name.is_empty() && name.chars().all(valid_char) { + Ok(name) + } + else { + Err(format!("package name `{}` is invalid", name)) + } +} + +fn print_help() -> Result<(), Box> { + eprintln!("trust-check: output aurto-untrusted package:maintainer"); + eprintln!(" Usage: trust-check PACKAGES..."); + Ok(()) +} + +#[test] +fn translate_full_package_for_package_name() { + assert_eq!(translate_full_package("aurto".into()), Ok("aurto".into())); + assert_eq!( + translate_full_package("gnome-shell-extension-arch-update".into()), + Ok("gnome-shell-extension-arch-update".into()), + ); +} + +#[test] +fn translate_full_package_for_archive_name() { + assert_eq!( + translate_full_package("/home/alex/tmp/aurto-0.6.7-2-any.pkg.tar.gz".into()), + Ok("aurto".into()), + ); + assert_eq!( + translate_full_package("../gnome-shell-extension-arch-update-26-1-any.pkg.tar.gz".into()), + Ok("gnome-shell-extension-arch-update".into()), + ); +}