aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig7
-rwxr-xr-xkiss196
2 files changed, 160 insertions, 43 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7b8413b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+
+# Force GitHub to display tabs
+# mixed with [4] spaces properly.
+[kiss]
+indent_style = tab
+indent_size = 4
diff --git a/kiss b/kiss
index c353568..28b0161 100755
--- a/kiss
+++ b/kiss
@@ -45,6 +45,106 @@ 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.
+ 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
+
+ # 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
+}
+
pkg_lint() {
# Check that each mandatory file in the package entry exists.
log "$1" "Checking repository files"
@@ -448,7 +548,15 @@ pkg_build() {
log "Building: $*"
# Only ask for confirmation if more than one package needs to be built.
- [ $# -gt 1 ] || [ "$pkg_update" ] && prompt
+ [ $# -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
+ }
log "Checking to see if any dependencies have already been built"
log "Installing any pre-built dependencies"
@@ -467,7 +575,9 @@ 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 args i "$bin_dir/$pkg#$version-$release.tar.gz")
+
+ (KISS_FORCE=1 \
+ pkg_install "$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'.
@@ -554,7 +664,9 @@ pkg_build() {
contains "$explicit" "$pkg" && [ -z "$pkg_update" ] && continue
log "$pkg" "Needed as a dependency or has an update, installing"
- (KISS_FORCE=1 args i "$pkg")
+
+ (KISS_FORCE=1 \
+ pkg_install "$bin_dir/$pkg#$version-$release.tar.gz")
done
# End here as this was a system update and all packages have been installed.
@@ -692,9 +804,9 @@ pkg_remove() {
[ "${file##/etc/*}" ] || continue
if [ -d "$KISS_ROOT/$file" ]; then
- rmdir "$KISS_ROOT/$file" 2>/dev/null || continue
+ dosu rmdir "'$KISS_ROOT/$file'" 2>/dev/null || continue
else
- rm -f "$KISS_ROOT/$file"
+ dosu rm -f "'$KISS_ROOT/$file'"
fi
done < "$sys_db/$1/manifest"
@@ -770,9 +882,9 @@ pkg_install() {
# This is repeated multiple times. Better to make it a function.
pkg_rsync() {
- rsync --chown=root:root --chmod=Du-s,Dg-s,Do-s \
+ dosu 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
@@ -781,8 +893,8 @@ pkg_install() {
# If '/etc/' exists in the package, install it but don't overwrite.
[ -d "$tar_dir/$pkg_name/etc" ] &&
- rsync --chown=root:root -WhHKa --no-compress --ignore-existing \
- "$tar_dir/$pkg_name/etc" "$KISS_ROOT/"
+ dosu 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" ] && {
@@ -799,18 +911,18 @@ pkg_install() {
# Remove files.
if [ -f "$file" ] && [ ! -L "$file" ]; then
- rm -f "$file"
+ dosu rm -f "'$file'"
# Remove file symlinks.
elif [ -L "$file" ] && [ ! -d "$file" ]; then
- unlink "$file" ||:
+ dosu unlink "'$file'" ||:
# Skip directory symlinks.
elif [ -L "$file" ] && [ -d "$file" ]; then :
# Remove directories if empty.
elif [ -d "$file" ]; then
- rmdir "$file" 2>/dev/null ||:
+ dosu rmdir "'$file'" 2>/dev/null ||:
fi
done ||:
}
@@ -826,7 +938,7 @@ pkg_install() {
if [ -x "$sys_db/$pkg_name/post-install" ]; then
log "$pkg_name" "Running post-install script"
- "$sys_db/$pkg_name/post-install" ||:
+ dosu "'$sys_db/$pkg_name/post-install'" ||:
fi
log "$pkg_name" "Installed successfully"
@@ -879,18 +991,20 @@ pkg_updates() {
if [ -w "$PWD" ]; then
git fetch
git merge
+
else
log "$PWD" "Need root to update"
- if command -v sudo >/dev/null; then
- sudo git fetch
- sudo git merge
- elif command -v doas >/dev/null; then
- doas git fetch
- doas git merge
- else
- su -c 'git fetch && git merge'
- fi
+ # 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)
fi
}
done
@@ -922,8 +1036,8 @@ pkg_updates() {
prompt
- pkg_build kiss
- args i kiss
+ pkg_build kiss
+ pkg_install kiss
log "Updated the package manager"
log "Re-run 'kiss update' to update your system"
@@ -956,6 +1070,8 @@ 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
+
[ "$KISS_DEBUG" != 1 ] || return
# Block 'Ctrl+C' while cache is being cleaned.
@@ -991,27 +1107,9 @@ args() {
# Parse some arguments earlier to remove the need to duplicate code.
case $action in
- c|checksum|s|search)
+ c|checksum|s|search|i|install|r|remove)
[ "$1" ] || die "'kiss $action' requires an argument"
;;
-
- 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.
- [ "$(id -u)" = 0 ] || {
- if command -v sudo >/dev/null; then
- sudo -E KISS_FORCE="$KISS_FORCE" kiss "$action" "$@"
- elif command -v doas >/dev/null; then
- KISS_FORCE="$KISS_FORCE" doas kiss "$action" "$@"
- else
- su -pc "KISS_FORCE=$KISS_FORCE kiss $action $*"
- fi
-
- return
- }
- ;;
esac
# Actions can be abbreviated to their first letter. This saves
@@ -1113,6 +1211,18 @@ 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 ] || die "kiss must be run as a normal user"
+
+ # 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