diff options
author | noreply@github.com <noreply@github.com> | 2020-01-30 09:06:19 +0000 |
---|---|---|
committer | noreply@github.com <noreply@github.com> | 2020-01-30 09:06:19 +0000 |
commit | f6965bc6ea342cce8e172c8a047de7c38b67d0ab (patch) | |
tree | ac2e8c0709dd443e73eb41f447bb1a2c2aef74fb | |
parent | 1b0876f6379a53d5d4809895f918b03467fe4a8d (diff) | |
parent | 6355643ed68379bad585e35ef5b1eac60b2b48bb (diff) | |
download | cpt-f6965bc6ea342cce8e172c8a047de7c38b67d0ab.tar.gz |
Merge pull request #101 from kisslinux/oldsu
Find a middleground between the old and new root model
FossilOrigin-Name: e46e581586373205a59ad0540d8a912cb8996ab09590dd709dc1aa6356aecd16
-rwxr-xr-x | kiss | 219 |
1 files changed, 55 insertions, 164 deletions
@@ -48,109 +48,18 @@ prompt() { read -r _ } -root_cache() { - # This function simply mimics a 'su' prompt to then store - # the root password for the lifetime of the package manager. - # - # This function is called once when needed to cache the - # password. The password is not accessible to any subprocesses - # and should never leave the package manager's process. - # - # This behavior is needed as there is no POSIX shell method - # of running a shell function as a different user. We can't - # selectively lower or raise permissions in a seamless way - # through "normal" means. - # - # Root is only needed when installing/removing packages whereas - # non-root permissions are needed in countless places throughout. - # - # This is the only *workable* solution to 1) not run the entire - # package manager as root and 2) avoid prompting for password - # before, during and after builds numerous times. - # - # NOTE: Careful consideration has been taken in regards to this - # change and I would have loved an inconspicuous solution - # to this problem... but it doesn't exist. - # - # This change was needed as the existing behavior was not ideal - # in any way and needed to be fixed. - - # Pointless running this function if the user has chosen to run - # the package manager as root anyway. - [ "$(id -u)" != 0 ] || return 0 - - printf 'Password: ' - - # Disable echoing to the terminal while the password is inputted - # by the user. The below commands read from '/dev/tty' to ensure - # they work when run from a subshell. - # - # The variable '$cached' is used to check if we've been here - # before. We cannot check whether or not '$pass' is empty as the - # '[' command may be external which would result in /proc leakage. - stty -F /dev/tty -echo - IFS= read -r pass < /dev/tty && cached=1 - stty -F /dev/tty echo - - printf '\n' - - # Validate the password now with a simple 'true' command as we - # don't yet need to elevate permissions. - dosu /bin/true -} - -dosu() { - [ "$cached" ] || root_cache +as_root() { + [ "$(id -u)" = 0 ] || { + log "Using '${su:-su}'" + + case $su in + *sudo) sudo -u "${user:-root}" -E sh -c "$*" ;; + *doas) doas -u "${user:-root}" sh -c "$*" ;; + *) su -pc "$* <&3" "${user:-root}" 3<&0 </dev/tty ;; + esac - # Declare this as a function to avoid repeating it twice - # below. Great naming of functions all around. - # - # Run a command as root using the cached password. The 'su' - # command allows you to input a password via stdin. To hide - # the prompt, the command's output is sent to '/dev/tty' - # and the output of 'su' is sent to '/dev/null'. - dosudo() { su "${drop_to:-root}" -c "$* >/dev/tty" >/dev/null; } - - # The code below uses the most secure method of sending - # data over stdin based on what is available in the system. - # - # The great debate: Use a heredoc or echo+pipe for password - # input over stdin? Time to explain. - # - # 1) 'printf | cmd' is the most secure IF 'printf' is built - # into the shell and NOT an external command. When 'printf' - # is external, the password WILL be leaked over '/proc'. - # - # Safe shells here are anything with a builtin 'printf', - # 'ash', 'dash', 'bash' and most other shells. - # - # 2) Using a heredoc is as secure as the above method (when - # builtin) IF and only IF the user's shell implements - # heredocs WITHOUT the use of temporary files (See bash!). - # - # When using heredocs and a temporary file the risk is a - # tiny window in which the input is available inside of - # a temporary file. - # - # 'ash' and 'dash' are safe here, 'bash' is not ('bash' - # falls under (1) however). - # - # Which is best? (order is best to worst) - # - # 1) builtin 'printf'. - # 2) heredocs with no temporary file. - # 3) heredocs with a temporary file. - # - # This code below follows the above ordering when deciding - # which method to use. The '$heredocs' variable is declared - # in 'main()' after a check to see if 'printf' is builtin. - if [ "$heredocs" ]; then - dosudo "$@" <<-EOF - $pass - EOF - else - printf '%s\n' "$pass" | dosudo "$@" - fi + exit + } } regex_escape() { @@ -571,15 +480,7 @@ pkg_build() { log "Building: $*" # Only ask for confirmation if more than one package needs to be built. - [ $# -gt 1 ] || [ "$pkg_update" ] && { - prompt - - # Prompt for password prior to the build if more than one package - # will be built and installed. No use in forcing the user to wait - # for the first password prompt (before caching) if it may take a - # long long while. - [ "$cached" ] || root_cache - } + [ $# -gt 1 ] || [ "$pkg_update" ] && prompt log "Checking to see if any dependencies have already been built" log "Installing any pre-built dependencies" @@ -598,9 +499,7 @@ pkg_build() { # to 'su' to elevate permissions. [ -f "$bin_dir/$pkg#$version-$release.tar.gz" ] && { log "$pkg" "Found pre-built binary, installing" - - (KISS_FORCE=1 \ - pkg_install "$bin_dir/$pkg#$version-$release.tar.gz") + (KISS_FORCE=1 args i "$bin_dir/$pkg#$version-$release.tar.gz") # Remove the now installed package from the build # list. No better way than using 'sed' in POSIX 'sh'. @@ -698,8 +597,7 @@ pkg_build() { log "$pkg" "Needed as a dependency or has an update, installing" - (KISS_FORCE=1 \ - pkg_install "$bin_dir/$pkg#$version-$release.tar.gz") + (KISS_FORCE=1 args i "$pkg") done # End here as this was a system update and all packages have been installed. @@ -890,9 +788,9 @@ pkg_swap() { # Convert the current owner to an alternative and rewrite # its manifest file to reflect this. - dosu cp -f "'$2'" "'$pkg_owns>${alt#*>}'" - dosu sed "'s/^$sea\$/$rep/'" \ - "'$mani' > '$mani.1' && mv -f '$mani.1' '$mani'" + cp -f "$2" "$pkg_owns>${alt#*>}" + sed "s/^$sea\$/$rep/" "$mani" > "$mani.1" + mv -f "$mani.1" "$mani" fi regex_escape "$PWD/$alt" "$2" @@ -900,9 +798,9 @@ pkg_swap() { # Convert the desired alternative to a real file and rewrite # the manifest file to reflect this. The reverse of above. - dosu mv -f "'$alt'" "'$2'" - dosu sed "'s/^$sea\$/$rep/'" \ - "'$mani' > '$mani.1' && mv -f '$mani.1' '$mani'" + mv -f "$alt" "$2" + sed "s/^$sea\$/$rep/" "$mani" > "$mani.1" + mv -f "$mani.1" "$mani" } pkg_remove() { @@ -949,9 +847,9 @@ pkg_remove() { [ "${file##/etc/*}" ] || continue if [ -d "$KISS_ROOT/$file" ]; then - dosu rmdir "'$KISS_ROOT/$file'" 2>/dev/null || continue + rmdir "$KISS_ROOT/$file" 2>/dev/null || continue else - dosu rm -f "'$KISS_ROOT/$file'" + rm -f "$KISS_ROOT/$file" fi done < "$sys_db/$1/manifest" @@ -995,7 +893,7 @@ pkg_install() { mkdir -p "$tar_dir/$pkg_name" # Extract the tar-ball to catch any errors before installation begins. - dosu tar pxf "'$tar_file'" -C "'$tar_dir/$pkg_name'" || + tar pxf "$tar_file" -C "$tar_dir/$pkg_name" || die "$pkg_name" "Failed to extract tar-ball" log "$pkg_name" "Checking that all dependencies are installed" @@ -1027,9 +925,9 @@ pkg_install() { # This is repeated multiple times. Better to make it a function. pkg_rsync() { - dosu rsync --chown=root:root --chmod=Du-s,Dg-s,Do-s \ + rsync --chown=root:root --chmod=Du-s,Dg-s,Do-s \ -WhHKa --no-compress "$1" --exclude /etc \ - "'$tar_dir/$pkg_name/'" "'$KISS_ROOT/'" + "$tar_dir/$pkg_name/" "$KISS_ROOT/" } # Install the package by using 'rsync' and overwrite any existing files @@ -1038,8 +936,8 @@ pkg_install() { # If '/etc/' exists in the package, install it but don't overwrite. [ -d "$tar_dir/$pkg_name/etc" ] && - dosu rsync --chown=root:root -WhHKa --no-compress --ignore-existing \ - "'$tar_dir/$pkg_name/etc'" "'$KISS_ROOT/'" + rsync --chown=root:root -WhHKa --no-compress --ignore-existing \ + "$tar_dir/$pkg_name/etc" "$KISS_ROOT/" # Remove any leftover files if this is an upgrade. [ "$old_manifest" ] && { @@ -1056,18 +954,18 @@ pkg_install() { # Remove files. if [ -f "$file" ] && [ ! -L "$file" ]; then - dosu rm -f "'$file'" + rm -f "$file" # Remove file symlinks. elif [ -L "$file" ] && [ ! -d "$file" ]; then - dosu unlink "'$file'" ||: + unlink "$file" ||: # Skip directory symlinks. elif [ -L "$file" ] && [ -d "$file" ]; then : # Remove directories if empty. elif [ -d "$file" ]; then - dosu rmdir "'$file'" 2>/dev/null ||: + rmdir "$file" 2>/dev/null ||: fi done ||: } @@ -1083,7 +981,7 @@ pkg_install() { if [ -x "$sys_db/$pkg_name/post-install" ]; then log "$pkg_name" "Running post-install script" - dosu "'$sys_db/$pkg_name/post-install'" ||: + "$sys_db/$pkg_name/post-install" ||: fi log "$pkg_name" "Installed successfully" @@ -1140,16 +1038,7 @@ pkg_updates() { else log "$PWD" "Need root to update" - # Find out the owner of the repository and spawn - # git as this user below. - # - # This prevents 'git' from changing the original - # ownership of files and directories in the rare - # case that the repository is owned by a 3rd user. - (drop_to=$(stat -c %U "$PWD") - - dosu git fetch - dosu git merge) + as_root 'git fetch && git merge' fi } done @@ -1181,8 +1070,8 @@ pkg_updates() { prompt - pkg_build kiss - pkg_install kiss + pkg_build kiss + args i kiss log "Updated the package manager" log "Re-run 'kiss update' to update your system" @@ -1215,7 +1104,6 @@ pkg_updates() { pkg_clean() { # Clean up on exit or error. This removes everything related # to the build. - stty -F /dev/tty echo 2>/dev/null # Block 'Ctrl+C' while cache is being cleaned. trap '' INT @@ -1246,17 +1134,31 @@ args() { *) case $@ in - *'*'*|*'!'*|*'['*|*']'*) - die "Arguments contain invalid characters: '!*[]'" + *'*'*|*'!'*|*'['*|*']'*|*' '*) + die "Arguments contain invalid characters: '!*[] '" ;; esac esac # Parse some arguments earlier to remove the need to duplicate code. case $action in - c|checksum|s|search|i|install|r|remove) + c|checksum|s|search) [ "$1" ] || die "'kiss $action' requires an argument" ;; + + a|alternatives) + # Rerun the script with 'su' if the user isn't root. + # Cheeky but 'su' can't be used on shell functions themselves. + [ -z "$1" ] || as_root kiss "$action" "$@" + ;; + + i|install|r|remove) + [ "$1" ] || die "'kiss $action' requires an argument" + + # Rerun the script with 'su' if the user isn't root. + # Cheeky but 'su' can't be used on shell functions themselves. + KISS_FORCE="$KISS_FORCE" as_root kiss "$action" "$@" + ;; esac # Actions can be abbreviated to their first letter. This saves @@ -1385,21 +1287,6 @@ args() { } main() { - # Ensure that debug mode is never enabled to prevent internal - # package manager information from leaking to stdout. - set +x - - # Prevent the package manager from running as root. The package - # manager will elevate permissions where needed. - [ "$(id -u)" != 0 ] || [ "$KISS_ASROOT" ] || { - log "kiss must be run as a normal user" "" "!>" - die "(Run with KISS_ASROOT=1 to ignore this warning)" - } - - # Use the most secure method of sending data over stdin based on - # whether or not the 'printf' command is built into the shell. - [ "$(command -v printf)" = printf ] || heredocs=1 - # Set the location to the repository and package database. pkg_db=var/db/kiss/installed @@ -1422,6 +1309,10 @@ main() { # POSIX correctness. grep=$(command -v ggrep) || grep='grep' + # Figure out which 'sudo' command to use based on the user's choice or + # what is available on the system. + su=${KISS_SU:-$(command -v sudo || command -v doas)} || su=su + # Store the date and time of script invocation to be used as the name # of the log files the package manager creates uring builds. time=$(date '+%d-%m-%Y-%H:%M') |