summaryrefslogtreecommitdiff
path: root/aptdpkgclean
blob: e42631ce1031b3172b3a874c3c29360ca8dcbbfa (plain)
  1. #!/bin/sh
  2. #
  3. # /usr/local/sbin/aptdpkgclean
  4. # Put together and tweaked by Jonas Smedegaard <dr@jones.dk>
  5. # 2001-2002
  6. #
  7. # $Id: aptdpkgclean,v 1.1 2002-03-07 16:22:51 jonas Exp $
  8. #
  9. # Auto re-mounting of a readonly /usr
  10. # Put the following in a file below /etc/apt/apt.conf.d:
  11. #
  12. # // Auto re-mounting of a readonly /usr
  13. # // Based on mail from Anthony Towns <aj@azure.humbug.org.au>
  14. # // http://lists.debian.org/debian-devel/2001/debian-devel-200111/msg00212.html
  15. #
  16. # // This is the intended thing:
  17. # // DPkg {
  18. # // Pre-Invoke {"mount -o remount,rw /usr";};
  19. # // Post-Invoke {"mount -o remount,ro /usr";};
  20. # // };
  21. #
  22. # DPkg {
  23. # Pre-Install-Pkgs {"/usr/local/sbin/aptdpkgro";};
  24. # Pre-Invoke {"mount -o remount,rw /usr";};
  25. # Post-Invoke {"/usr/local/sbin/aptdpkgclean; mount -o remount,ro /usr";};
  26. # };
  27. #
  28. pathmatch="^/usr"
  29. cat /var/lib/my_ro_hack.todel | while read file; do
  30. [ -f "$file" ] || continue
  31. N1=`find "$file" -printf "%i\n"`
  32. b=`basename $file`; d=`dirname $file`
  33. XF="${b#.}"; XF="$d/${XF%.dpkg-ro-used.*}"
  34. N2=`find "$XF" -printf "%i\n"` || continue
  35. [ -n "$XF" ] || continue
  36. if [ "$N1" != "$N2" ] && ! fuser -s "$file"; then
  37. rm -f "$file"
  38. else
  39. echo "$file"
  40. fi
  41. done >/var/lib/my_ro_hack.todel.new
  42. mv /var/lib/my_ro_hack.todel.new /var/lib/my_ro_hack.todel
ity (all caps).
  • # separate with $IFS explicitly, since we do some fancy footwork
  • # elsewhere.
  • alllevels="DEBUG${IFS}VERBOSE${IFS}INFO${IFS}ERROR"
  • # translate lowers to uppers in global log level
  • LOG_LEVEL=$(echo "$LOG_LEVEL" | tr "[:lower:]" "[:upper:]")
  • # just go ahead and return if the log level is silent
  • if [ "$LOG_LEVEL" = 'SILENT' ] ; then
  • return
  • fi
  • for level in $alllevels ; do
  • if [ "$LOG_LEVEL" = "$level" ] ; then
  • found=true
  • fi
  • done
  • if [ -z "$found" ] ; then
  • # default to INFO:
  • LOG_LEVEL=INFO
  • fi
  • # get priority from first parameter, translating all lower to
  • # uppers
  • priority=$(echo "$1" | tr "[:lower:]" "[:upper:]")
  • shift
  • # scan over available levels
  • for level in $alllevels ; do
  • # output if the log level matches, set output to true
  • # this will output for all subsequent loops as well.
  • if [ "$LOG_LEVEL" = "$level" ] ; then
  • output=true
  • fi
  • if [ "$priority" = "$level" -a "$output" = 'true' ] ; then
  • if [ "$1" ] ; then
  • echo "$@"
  • else
  • cat
  • fi | sed 's/^/'"${LOG_PREFIX}"'/' >&2
  • fi
  • done
  • }
  • # run command as monkeysphere user
  • su_monkeysphere_user() {
  • # our main goal here is to run the given command as the the
  • # monkeysphere user, but without prompting for any sort of
  • # authentication. If this is not possible, we should just fail.
  • # FIXME: our current implementation is overly restrictive, because
  • # there may be some su PAM configurations that would allow su
  • # "$MONKEYSPHERE_USER" -c "$@" to Just Work without prompting,
  • # allowing specific users to invoke commands which make use of
  • # this user.
  • # chpst (from runit) would be nice to use, but we don't want to
  • # introduce an extra dependency just for this. This may be a
  • # candidate for re-factoring if we switch implementation languages.
  • case $(id -un) in
  • # if monkeysphere user, run the command under bash
  • "$MONKEYSPHERE_USER")
  • bash -c "$@"
  • ;;
  • # if root, su command as monkeysphere user
  • 'root')
  • su "$MONKEYSPHERE_USER" -c "$@"
  • ;;
  • # otherwise, fail
  • *)
  • log error "non-privileged user."
  • ;;
  • esac
  • }
  • # cut out all comments(#) and blank lines from standard input
  • meat() {
  • grep -v -e "^[[:space:]]*#" -e '^$' "$1"
  • }
  • # cut a specified line from standard input
  • cutline() {
  • head --line="$1" "$2" | tail -1
  • }
  • # make a temporary directory
  • msmktempdir() {
  • mktemp -d ${TMPDIR:-/tmp}/monkeysphere.XXXXXXXXXX
  • }
  • # make a temporary file
  • msmktempfile() {
  • mktemp ${TMPDIR:-/tmp}/monkeysphere.XXXXXXXXXX
  • }
  • # this is a wrapper for doing lock functions.
  • #
  • # it lets us depend on either lockfile-progs (preferred) or procmail's
  • # lockfile, and should
  • lock() {
  • local use_lockfileprogs=true
  • local action="$1"
  • local file="$2"
  • if ! ( type lockfile-create &>/dev/null ) ; then
  • if ! ( type lockfile &>/dev/null ); then
  • failure "Neither lockfile-create nor lockfile are in the path!"
  • fi
  • use_lockfileprogs=
  • fi
  • case "$action" in
  • create)
  • if [ -n "$use_lockfileprogs" ] ; then
  • lockfile-create "$file" || failure "unable to lock '$file'"
  • else
  • lockfile -r 20 "${file}.lock" || failure "unable to lock '$file'"
  • fi
  • log debug "lock created on '$file'."
  • ;;
  • touch)
  • if [ -n "$use_lockfileprogs" ] ; then
  • lockfile-touch --oneshot "$file"
  • else
  • : Nothing to do here
  • fi
  • log debug "lock touched on '$file'."
  • ;;
  • remove)
  • if [ -n "$use_lockfileprogs" ] ; then
  • lockfile-remove "$file"
  • else
  • rm -f "${file}.lock"
  • fi
  • log debug "lock removed on '$file'."
  • ;;
  • *)
  • failure "bad argument for lock subfunction '$action'"
  • esac
  • }
  • # for portability, between gnu date and BSD date.
  • # arguments should be: number longunits format
  • # e.g. advance_date 20 seconds +%F
  • advance_date() {
  • local gnutry
  • local number="$1"
  • local longunits="$2"
  • local format="$3"
  • local shortunits
  • # try things the GNU way first
  • if date -d "$number $longunits" "$format" &>/dev/null; then
  • date -d "$number $longunits" "$format"
  • else
  • # otherwise, convert to (a limited version of) BSD date syntax:
  • case "$longunits" in
  • years)
  • shortunits=y
  • ;;
  • months)
  • shortunits=m
  • ;;
  • weeks)
  • shortunits=w
  • ;;
  • days)
  • shortunits=d
  • ;;
  • hours)
  • shortunits=H
  • ;;
  • minutes)
  • shortunits=M
  • ;;
  • seconds)
  • shortunits=S
  • ;;
  • *)
  • # this is a longshot, and will likely fail; oh well.
  • shortunits="$longunits"
  • esac
  • date "-v+${number}${shortunits}" "$format"
  • fi
  • }
  • # check that characters are in a string (in an AND fashion).
  • # used for checking key capability
  • # check_capability capability a [b...]
  • check_capability() {
  • local usage
  • local capcheck
  • usage="$1"
  • shift 1
  • for capcheck ; do
  • if echo "$usage" | grep -q -v "$capcheck" ; then
  • return 1
  • fi
  • done
  • return 0
  • }
  • # hash of a file
  • file_hash() {
  • if type md5sum &>/dev/null ; then
  • md5sum "$1"
  • elif type md5 &>/dev/null ; then
  • md5 "$1"
  • else
  • failure "Neither md5sum nor md5 are in the path!"
  • fi
  • }
  • # convert escaped characters in pipeline from gpg output back into
  • # original character
  • # FIXME: undo all escape character translation in with-colons gpg
  • # output
  • gpg_unescape() {
  • sed 's/\\x3a/:/g'
  • }
  • # convert nasty chars into gpg-friendly form in pipeline
  • # FIXME: escape everything, not just colons!
  • gpg_escape() {
  • sed 's/:/\\x3a/g'
  • }
  • # prompt for GPG-formatted expiration, and emit result on stdout
  • get_gpg_expiration() {
  • local keyExpire
  • keyExpire="$1"
  • if [ -z "$keyExpire" -a "$PROMPT" = 'true' ]; then
  • cat >&2 <<EOF
  • Please specify how long the key should be valid.
  • 0 = key does not expire
  • <n> = key expires in n days
  • <n>w = key expires in n weeks
  • <n>m = key expires in n months
  • <n>y = key expires in n years
  • EOF
  • while [ -z "$keyExpire" ] ; do
  • printf "Key is valid for? (0) " >&2
  • read keyExpire
  • if ! test_gpg_expire ${keyExpire:=0} ; then
  • echo "invalid value" >&2
  • unset keyExpire
  • fi
  • done
  • elif ! test_gpg_expire "$keyExpire" ; then
  • failure "invalid key expiration value '$keyExpire'."
  • fi
  • echo "$keyExpire"
  • }
  • passphrase_prompt() {
  • local prompt="$1"
  • local fifo="$2"
  • local PASS
  • if [ "$DISPLAY" ] && type "${SSH_ASKPASS:-ssh-askpass}" >/dev/null; then
  • printf 'Launching "%s"\n' "${SSH_ASKPASS:-ssh-askpass}" | log info
  • printf '(with prompt "%s")\n' "$prompt" | log debug
  • "${SSH_ASKPASS:-ssh-askpass}" "$prompt" > "$fifo"
  • else
  • read -s -p "$prompt" PASS
  • # Uses the builtin echo, so should not put the passphrase into
  • # the process table. I think. --dkg
  • echo "$PASS" > "$fifo"
  • fi
  • }
  • # remove all lines with specified string from specified file
  • remove_line() {
  • local file
  • local string
  • local tempfile
  • file="$1"
  • string="$2"
  • if [ -z "$file" -o -z "$string" ] ; then
  • return 1
  • fi
  • if [ ! -e "$file" ] ; then
  • return 1
  • fi
  • # if the string is in the file...
  • if grep -q -F "$string" "$file" 2>/dev/null ; then
  • tempfile=$(mktemp "${file}.XXXXXXX") || \
  • failure "Unable to make temp file '${file}.XXXXXXX'"
  • # remove the line with the string, and return 0
  • grep -v -F "$string" "$file" >"$tempfile"
  • cat "$tempfile" > "$file"
  • rm "$tempfile"
  • return 0
  • # otherwise return 1
  • else
  • return 1
  • fi
  • }
  • # remove all lines with MonkeySphere strings in file
  • remove_monkeysphere_lines() {
  • local file
  • local tempfile
  • file="$1"
  • # return error if file does not exist
  • if [ ! -e "$file" ] ; then
  • return 1
  • fi
  • # just return ok if the file is empty, since there aren't any
  • # lines to remove
  • if [ ! -s "$file" ] ; then
  • return 0
  • fi
  • tempfile=$(mktemp "${file}.XXXXXXX") || \
  • failure "Could not make temporary file '${file}.XXXXXXX'."
  • egrep -v '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' \
  • "$file" >"$tempfile"
  • cat "$tempfile" > "$file"
  • rm "$tempfile"
  • }
  • # translate ssh-style path variables %h and %u
  • translate_ssh_variables() {
  • local uname
  • local home
  • uname="$1"
  • path="$2"
  • # get the user's home directory
  • userHome=$(get_homedir "$uname")
  • # translate '%u' to user name
  • path=${path/\%u/"$uname"}
  • # translate '%h' to user home directory
  • path=${path/\%h/"$userHome"}
  • echo "$path"
  • }
  • # test that a string to conforms to GPG's expiration format
  • test_gpg_expire() {
  • echo "$1" | egrep -q "^[0-9]+[mwy]?$"
  • }
  • # check that a file is properly owned, and that all it's parent
  • # directories are not group/other writable
  • check_key_file_permissions() {
  • local uname
  • local path
  • local stat
  • local access
  • local gAccess
  • local oAccess
  • # function to check that the given permission corresponds to writability
  • is_write() {
  • [ "$1" = "w" ]
  • }
  • uname="$1"
  • path="$2"
  • log debug "checking path permission '$path'..."
  • # rewrite path if it points to a symlink
  • if [ -h "$path" ] ; then
  • path=$(readlink -f "$path")
  • log debug "checking path symlink '$path'..."
  • fi
  • # return 255 if cannot stat file
  • if ! stat=$(ls -ld "$path" 2>/dev/null) ; then
  • log error "could not stat path '$path'."
  • return 255
  • fi
  • owner=$(echo "$stat" | awk '{ print $3 }')
  • gAccess=$(echo "$stat" | cut -c6)
  • oAccess=$(echo "$stat" | cut -c9)
  • # return 1 if path has invalid owner
  • if [ "$owner" != "$uname" -a "$owner" != 'root' ] ; then
  • log error "improper ownership on path '$path':"
  • log error " $owner != ($uname|root)"
  • return 1
  • fi
  • # return 2 if path has group or other writability
  • if is_write "$gAccess" || is_write "$oAccess" ; then
  • log error "improper group or other writability on path '$path':"
  • log error " group: $gAccess, other: $oAccess"
  • return 2
  • fi
  • # return zero if all clear, or go to next path
  • if [ "$path" = '/' ] ; then
  • log debug "path ok."
  • return 0
  • else
  • check_key_file_permissions "$uname" $(dirname "$path")
  • fi
  • }
  • # return a list of all users on the system
  • list_users() {
  • if type getent &>/dev/null ; then
  • # for linux and FreeBSD systems
  • getent passwd | cut -d: -f1
  • elif type dscl &>/dev/null ; then
  • # for Darwin systems
  • dscl localhost -list /Search/Users
  • else
  • failure "Neither getent or dscl is in the path! Could not determine list of users."
  • fi
  • }
  • # return the path to the home directory of a user
  • get_homedir() {
  • local uname=${1:-`whoami`}
  • eval "echo ~${uname}"
  • }
  • # return the primary group of a user
  • get_primary_group() {
  • local uname=${1:-`whoami`}
  • groups "$uname" | sed 's/^..* : //' | awk '{ print $1 }'
  • }
  • ### CONVERSION UTILITIES
  • # output the ssh key for a given key ID
  • gpg2ssh() {
  • local keyID
  • keyID="$1"
  • gpg --export "$keyID" | openpgp2ssh "$keyID" 2>/dev/null
  • }
  • # output known_hosts line from ssh key
  • ssh2known_hosts() {
  • local host
  • local port
  • local key
  • # FIXME this does not properly deal with IPv6 hosts using the
  • # standard port (because it's unclear whether their final
  • # colon-delimited address section is a port number or an address
  • # string)
  • host=${1%:*}
  • port=${1##*:}
  • key="$2"
  • # specify the host and port properly for new ssh known_hosts
  • # format
  • if [ "$port" != "$host" ] ; then
  • host="[${host}]:${port}"
  • fi
  • printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE"
  • }
  • # output authorized_keys line from ssh key
  • ssh2authorized_keys() {
  • local userID
  • local key
  • userID="$1"
  • key="$2"
  • printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID"
  • }
  • # convert key from gpg to ssh known_hosts format
  • gpg2known_hosts() {
  • local host
  • local keyID
  • local key
  • host="$1"
  • keyID="$2"
  • key=$(gpg2ssh "$keyID")
  • # NOTE: it seems that ssh-keygen -R removes all comment fields from
  • # all lines in the known_hosts file. why?
  • # NOTE: just in case, the COMMENT can be matched with the
  • # following regexp:
  • # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  • printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE"
  • }
  • # convert key from gpg to ssh authorized_keys format
  • gpg2authorized_keys() {
  • local userID
  • local keyID
  • local key
  • userID="$1"
  • keyID="$2"
  • key=$(gpg2ssh "$keyID")
  • # NOTE: just in case, the COMMENT can be matched with the
  • # following regexp:
  • # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  • printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID"
  • }
  • ### GPG UTILITIES
  • # retrieve all keys with given user id from keyserver
  • # FIXME: need to figure out how to retrieve all matching keys
  • # (not just first N (5 in this case))
  • gpg_fetch_userid() {
  • local returnCode=0
  • local userID
  • if [ "$CHECK_KEYSERVER" != 'true' ] ; then
  • return 0
  • fi
  • userID="$1"
  • log verbose " checking keyserver $KEYSERVER... "
  • echo 1,2,3,4,5 | \
  • gpg --quiet --batch --with-colons \
  • --command-fd 0 --keyserver "$KEYSERVER" \
  • --search ="$userID" &>/dev/null
  • returnCode="$?"
  • return "$returnCode"
  • }
  • ########################################################################
  • ### PROCESSING FUNCTIONS
  • # userid and key policy checking
  • # the following checks policy on the returned keys
  • # - checks that full key has appropriate valididy (u|f)
  • # - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY)
  • # - checks that requested user ID has appropriate validity
  • # (see /usr/share/doc/gnupg/DETAILS.gz)
  • # output is one line for every found key, in the following format:
  • #
  • # flag:sshKey
  • #
  • # "flag" is an acceptability flag, 0 = ok, 1 = bad
  • # "sshKey" is the translated gpg key
  • #
  • # all log output must go to stderr, as stdout is used to pass the
  • # flag:sshKey to the calling function.
  • #
  • # expects global variable: "MODE"
  • process_user_id() {
  • local returnCode=0
  • local userID
  • local requiredCapability
  • local requiredPubCapability
  • local gpgOut
  • local type
  • local validity
  • local keyid
  • local uidfpr
  • local usage
  • local keyOK
  • local uidOK
  • local lastKey
  • local lastKeyOK
  • local fingerprint
  • userID="$1"
  • # set the required key capability based on the mode
  • if [ "$MODE" = 'known_hosts' ] ; then
  • requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY"
  • elif [ "$MODE" = 'authorized_keys' ] ; then
  • requiredCapability="$REQUIRED_USER_KEY_CAPABILITY"
  • fi
  • requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]")
  • # fetch the user ID if necessary/requested
  • gpg_fetch_userid "$userID"
  • # output gpg info for (exact) userid and store
  • gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
  • --with-fingerprint --with-fingerprint \
  • ="$userID" 2>/dev/null) || returnCode="$?"
  • # if the gpg query return code is not 0, return 1
  • if [ "$returnCode" -ne 0 ] ; then
  • log verbose " no primary keys found."
  • return 1
  • fi
  • # loop over all lines in the gpg output and process.
  • echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
  • while IFS=: read -r type validity keyid uidfpr usage ; do
  • # process based on record type
  • case $type in
  • 'pub') # primary keys
  • # new key, wipe the slate
  • keyOK=
  • uidOK=
  • lastKey=pub
  • lastKeyOK=
  • fingerprint=
  • log verbose " primary key found: $keyid"
  • # if overall key is not valid, skip
  • if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  • log debug " - unacceptable primary key validity ($validity)."
  • continue
  • fi
  • # if overall key is disabled, skip
  • if check_capability "$usage" 'D' ; then
  • log debug " - key disabled."
  • continue
  • fi
  • # if overall key capability is not ok, skip
  • if ! check_capability "$usage" $requiredPubCapability ; then
  • log debug " - unacceptable primary key capability ($usage)."
  • continue
  • fi
  • # mark overall key as ok
  • keyOK=true
  • # mark primary key as ok if capability is ok
  • if check_capability "$usage" $requiredCapability ; then
  • lastKeyOK=true
  • fi
  • ;;
  • 'uid') # user ids
  • if [ "$lastKey" != pub ] ; then
  • log verbose " ! got a user ID after a sub key?! user IDs should only follow primary keys!"
  • continue
  • fi
  • # if an acceptable user ID was already found, skip
  • if [ "$uidOK" = 'true' ] ; then
  • continue
  • fi
  • # if the user ID does matches...
  • if [ "$(echo "$uidfpr" | gpg_unescape)" = "$userID" ] ; then
  • # and the user ID validity is ok
  • if [ "$validity" = 'u' -o "$validity" = 'f' ] ; then
  • # mark user ID acceptable
  • uidOK=true
  • else
  • log debug " - unacceptable user ID validity ($validity)."
  • fi
  • else
  • continue
  • fi
  • # output a line for the primary key
  • # 0 = ok, 1 = bad
  • if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
  • log verbose " * acceptable primary key."
  • if [ -z "$sshKey" ] ; then
  • log error " ! primary key could not be translated (not RSA?)."
  • else
  • echo "0:${sshKey}"
  • fi
  • else
  • log debug " - unacceptable primary key."
  • if [ -z "$sshKey" ] ; then
  • log debug " ! primary key could not be translated (not RSA?)."
  • else
  • echo "1:${sshKey}"
  • fi
  • fi
  • ;;
  • 'sub') # sub keys
  • # unset acceptability of last key
  • lastKey=sub
  • lastKeyOK=
  • fingerprint=
  • # don't bother with sub keys if the primary key is not valid
  • if [ "$keyOK" != true ] ; then
  • continue
  • fi
  • # don't bother with sub keys if no user ID is acceptable:
  • if [ "$uidOK" != true ] ; then
  • continue
  • fi
  • # if sub key validity is not ok, skip
  • if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  • log debug " - unacceptable sub key validity ($validity)."
  • continue
  • fi
  • # if sub key capability is not ok, skip
  • if ! check_capability "$usage" $requiredCapability ; then
  • log debug " - unacceptable sub key capability ($usage)."
  • continue
  • fi
  • # mark sub key as ok
  • lastKeyOK=true
  • ;;
  • 'fpr') # key fingerprint