universalize and consolidate on --fixed-list-mode
[monkeysphere.git] / src / share / common
index 8e2232d659350558e472bd5f37c365d050317b8d..baf4270c0b4b749b488fd78ec9b3203a03963a7e 100644 (file)
@@ -103,12 +103,12 @@ su_monkeysphere_user() {
     case $(id -un) in
        # if monkeysphere user, run the command under bash
        "$MONKEYSPHERE_USER")
-           bash -c "$@"
+           bash -c "$*"
            ;;
 
          # if root, su command as monkeysphere user
        'root')
-           su "$MONKEYSPHERE_USER" -c "$@"
+           su "$MONKEYSPHERE_USER" -c "$*"
            ;;
 
        # otherwise, fail
@@ -281,7 +281,7 @@ get_gpg_expiration() {
 
     keyExpire="$1"
 
-    if [ -z "$keyExpire" -a "$PROMPT" = 'true' ]; then
+    if [ -z "$keyExpire" -a "$PROMPT" != 'false' ]; then
        cat >&2 <<EOF
 Please specify how long the key should be valid.
          0 = key does not expire
@@ -310,7 +310,7 @@ passphrase_prompt() {
     local fifo="$2"
     local PASS
 
-    if [ "$DISPLAY" ] && type "${SSH_ASKPASS:-ssh-askpass}" >/dev/null; then
+    if [ "$DISPLAY" ] && type "${SSH_ASKPASS:-ssh-askpass}" >/dev/null 2>/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"
@@ -325,61 +325,35 @@ passphrase_prompt() {
 # remove all lines with specified string from specified file
 remove_line() {
     local file
-    local string
+    local lines
     local tempfile
 
     file="$1"
-    string="$2"
+    shift
 
-    if [ -z "$file" -o -z "$string" ] ; then
+    if [ ! -e "$file" ] ; then
        return 1
     fi
 
-    if [ ! -e "$file" ] ; then
-       return 1
+    if (($# == 1)) ; then
+       lines=$(grep -F "$1" "$file") || true
+    else
+       lines=$(grep -F "$1" "$file" | grep -F "$2") || true
     fi
 
-    # if the string is in the file...
-    if grep -q -F "$string" "$file" 2>/dev/null ; then
+    # if the string was found, remove it
+    if [ "$lines" ] ; then
+       log debug "removing matching key lines..."
        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
+       grep -v -x -F "$lines" "$file" >"$tempfile" || :
+       mv -f "$tempfile" "$file"
     fi
 }
 
-# remove all lines with MonkeySphere strings in file
+# remove all lines with MonkeySphere strings from stdin
 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"
+    egrep -v ' MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2} '
 }
 
 # translate ssh-style path variables %h and %u
@@ -406,63 +380,40 @@ test_gpg_expire() {
     echo "$1" | egrep -q "^[0-9]+[mwy]?$"
 }
 
+# touch a key file if it doesn't exist, including creating needed
+# directories with correct permissions
+touch_key_file_or_fail() {
+    local keyFile="$1"
+    local newUmask
+
+    if [ ! -f "$keyFile" ]; then
+       # make sure to create files and directories with the
+       # appropriate write bits turned off:
+       newUmask=$(printf "%04o" $(( 0$(umask) | 0022 )) )
+       [ -d $(dirname "$keyFile") ] \
+           || (umask "$newUmask" && mkdir -p -m 0700 $(dirname "$keyFile") ) \
+           || failure "Could not create path to $keyFile"
+       # make sure to create this file with the appropriate bits turned off:
+       (umask "$newUmask" && touch "$keyFile") \
+           || failure "Unable to create $keyFile"
+    fi
+}
+
 # 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."
+    if [ "$STRICT_MODES" = 'false' ] ; then
+       log debug "skipping path permission check for '$path' because STRICT_MODES is false..."
        return 0
-    else
-       check_key_file_permissions "$uname" $(dirname "$path")
     fi
+    log debug "checking path permission '$path'..."
+    "${SYSSHAREDIR}/checkperms" "$uname" "$path"
 }
 
 # return a list of all users on the system
@@ -478,6 +429,28 @@ list_users() {
     fi
 }
 
+# take one argument, a service name.  in response, print a series of
+# lines, each with a unique numeric port number that might be
+# associated with that service name.  (e.g. in: "https", out: "443")
+# if nothing is found, print nothing, and return 0.
+# 
+# return 1 if there was an error in the search somehow
+get_port_for_service() {
+
+    [[ "$1" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]] || \
+        failure $(printf "This is not a valid service name: '%s'" "$1")
+    if type getent &>/dev/null ; then
+        # for linux and FreeBSD systems (getent returns 2 if not found, 0 on success, 1 or 3 on various failures)
+        (getent services "$service" || if [ "$?" -eq 2 ] ; then true ; else false; fi) | awk '{ print $2 }' | cut -f1 -d/ | sort -u
+    elif [ -r /etc/services ] ; then
+        # fall back to /etc/services for systems that don't have getent (MacOS?)
+        # FIXME: doesn't handle aliases like "null" (or "http"?), which don't show up at the beginning of the line.
+        awk $(printf '/^%s[[:space:]]/{ print $2 }' "$1") /etc/services | cut -f1 -d/ | sort -u
+    else
+        return 1
+    fi
+}
+
 # return the path to the home directory of a user
 get_homedir() {
     local uname=${1:-`whoami`}
@@ -498,7 +471,7 @@ gpg2ssh() {
     
     keyID="$1"
 
-    gpg --export "$keyID" | openpgp2ssh "$keyID" 2>/dev/null
+    gpg --export --no-armor "$keyID" | openpgp2ssh "$keyID" 2>/dev/null
 }
 
 # output known_hosts line from ssh key
@@ -520,18 +493,48 @@ ssh2known_hosts() {
     if [ "$port" != "$host" ] ; then
        host="[${host}]:${port}"
     fi
-    printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE"
+
+    # hash if specified
+    if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then
+       if (type ssh-keygen >/dev/null) ; then
+           log verbose "hashing known_hosts line"
+           # FIXME: this is really hackish cause
+           # ssh-keygen won't hash from stdin to
+           # stdout
+           tmpfile=$(mktemp ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
+           printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE" \
+               > "$tmpfile"
+           ssh-keygen -H -f "$tmpfile" 2>/dev/null
+           if [[ "$keyFile" == '-' ]] ; then
+               cat "$tmpfile"
+           else
+               cat "$tmpfile" >> "$keyFile"
+           fi
+           rm -f "$tmpfile" "${tmpfile}.old"
+           # FIXME: we could do this without needing ssh-keygen.
+           # hashed known_hosts looks like: |1|X|Y where 1 means SHA1
+           # (nothing else is defined in openssh sources), X is the
+           # salt (same length as the digest output), base64-encoded,
+           # and Y is the digested hostname (also base64-encoded).
+           # see hostfile.{c,h} in openssh sources.
+       else
+           log error "Cannot hash known_hosts line as requested."
+       fi
+    else
+       printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE"
+    fi
 }
 
 # output authorized_keys line from ssh key
 ssh2authorized_keys() {
-    local userID
-    local key
-    
-    userID="$1"
-    key="$2"
+    local userID="$1"
+    local key="$2"
 
-    printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID"
+    if [[ "$AUTHORIZED_KEYS_OPTIONS" ]]; then
+        printf "%s %s MonkeySphere%s %s\n" "$AUTHORIZED_KEYS_OPTIONS" "$key" "$DATE" "$userID"
+    else
+       printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID"
+    fi
 }
 
 # convert key from gpg to ssh known_hosts format
@@ -572,6 +575,15 @@ gpg2authorized_keys() {
 
 ### GPG UTILITIES
 
+# script to determine if gpg version is equal to or greater than specified version
+is_gpg_version_greater_equal() {
+    local gpgVersion=$(gpg --version | head -1 | awk '{ print $3 }')
+    local latest=$(printf '%s\n%s\n' "$1" "$gpgVersion" \
+       | tr '.' ' ' | sort -g -k1 -k2 -k3 \
+       | tail -1 | tr ' ' '.')
+    [[ "$gpgVersion" == "$latest" ]]
+}
+
 # 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))
@@ -592,6 +604,10 @@ gpg_fetch_userid() {
        --search ="$userID" &>/dev/null
     returnCode="$?"
 
+    if [ "$returnCode" != 0 ] ; then
+        log error "Failure ($returnCode) searching keyserver $KEYSERVER for user id '$userID'"
+    fi
+
     return "$returnCode"
 }
 
@@ -601,7 +617,7 @@ gpg_fetch_userid() {
 # 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 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:
@@ -609,15 +625,13 @@ gpg_fetch_userid() {
 # flag:sshKey
 #
 # "flag" is an acceptability flag, 0 = ok, 1 = bad
-# "sshKey" is the translated gpg key
+# "sshKey" is the relevant OpenPGP key, in the form accepted by OpenSSH
 #
 # 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 userID="$1"
     local requiredCapability
     local requiredPubCapability
     local gpgOut
@@ -632,21 +646,15 @@ process_user_id() {
     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
+    requiredCapability=${REQUIRED_KEY_CAPABILITY:="a"}
     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 \
+    gpgOut=$(gpg --list-key --fixed-list-mode --with-colons \
        --with-fingerprint --with-fingerprint \
        ="$userID" 2>/dev/null) || returnCode="$?"
 
@@ -722,7 +730,7 @@ process_user_id() {
                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?)."
+                       log verbose "    ! primary key could not be translated (not RSA?)."
                    else
                        echo "0:${sshKey}"
                    fi
@@ -800,361 +808,128 @@ process_user_id() {
     # being processed in the key files over "bad" keys (key flag '1')
 }
 
-# process a single host in the known_host file
-process_host_known_hosts() {
+process_keys_for_file() {
+    local keyFile="$1"
+    local userID="$2"
     local host
-    local userID
-    local noKey=
-    local nKeys
-    local nKeysOK
     local ok
     local sshKey
-    local tmpfile
-
-    # set the key processing mode
-    export MODE='known_hosts'
-
-    host="$1"
-    userID="ssh://${host}"
-
-    log verbose "processing: $host"
+    local keyLine
 
-    nKeys=0
-    nKeysOK=0
+    log verbose "processing: $userID"
+    log debug "key file: $keyFile"
 
     IFS=$'\n'
-    for line in $(process_user_id "${userID}") ; do
-       # note that key was found
-       nKeys=$((nKeys+1))
-
-       ok=$(echo "$line" | cut -d: -f1)
-       sshKey=$(echo "$line" | cut -d: -f2)
+    for line in $(process_user_id "$userID") ; do
+       ok=${line%%:*}
+       sshKey=${line#*:}
 
         if [ -z "$sshKey" ] ; then
             continue
         fi
 
-       # remove any old host key line, and note if removed nothing is
-       # removed
-       remove_line "$KNOWN_HOSTS" "$sshKey" || noKey=true
+       # remove the old key line
+       if [[ "$keyFile" != '-' ]] ; then
+           case "$FILE_TYPE" in
+               ('authorized_keys')
+                   remove_line "$keyFile" "$sshKey"
+                   ;;
+               ('known_hosts')
+                   host=${userID#ssh://}
+                   remove_line "$keyFile" "$host" "$sshKey"
+                   ;;
+           esac
+       fi
 
-       # if key OK, add new host line
+       ((++KEYS_PROCESSED))
+
+       # if key OK, add new key line
        if [ "$ok" -eq '0' ] ; then
-           # note that key was found ok
-           nKeysOK=$((nKeysOK+1))
-
-           # hash if specified
-           if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then
-               # FIXME: this is really hackish cause ssh-keygen won't
-               # hash from stdin to stdout
-               tmpfile=$(mktemp ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
-               ssh2known_hosts "$host" "$sshKey" > "$tmpfile"
-               ssh-keygen -H -f "$tmpfile" 2>/dev/null
-               cat "$tmpfile" >> "$KNOWN_HOSTS"
-               rm -f "$tmpfile" "${tmpfile}.old"
+           case "$FILE_TYPE" in
+               ('raw')
+                   keyLine="$sshKey"
+                   ;;
+               ('authorized_keys')
+                   keyLine=$(ssh2authorized_keys "$userID" "$sshKey")
+                   ;;
+               ('known_hosts')
+                   host=${userID#ssh://}
+                   keyLine=$(ssh2known_hosts "$host" "$sshKey")
+                   ;;
+           esac
+
+           echo "key line: $keyLine" | log debug
+           if [[ "$keyFile" == '-' ]] ; then
+               echo "$keyLine"
            else
-               ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS"
+               log debug "adding key line to file..."
+               echo "$keyLine" >>"$keyFile"
            fi
 
-           # log if this is a new key to the known_hosts file
-           if [ "$noKey" ] ; then
-               log info "* new key for $host added to known_hosts file."
-           fi
+           ((++KEYS_VALID))
        fi
     done
 
-    # if at least one key was found...
-    if [ "$nKeys" -gt 0 ] ; then
-       # if ok keys were found, return 0
-       if [ "$nKeysOK" -gt 0 ] ; then
-           return 0
-       # else return 2
-       else
-           return 2
-       fi
-    # if no keys were found, return 1
-    else
-       return 1
-    fi
+    log debug "KEYS_PROCESSED=$KEYS_PROCESSED"
+    log debug "KEYS_VALID=$KEYS_VALID"
 }
 
-# update the known_hosts file for a set of hosts listed on command
-# line
-update_known_hosts() {
-    local returnCode=0
-    local nHosts
-    local nHostsOK
-    local nHostsBAD
-    local fileCheck
-    local host
-
-    # the number of hosts specified on command line
-    nHosts="$#"
-
-    nHostsOK=0
-    nHostsBAD=0
-
-    # touch the known_hosts file so that the file permission check
-    # below won't fail upon not finding the file
-    (umask 0022 && touch "$KNOWN_HOSTS")
-
-    # check permissions on the known_hosts file path
-    check_key_file_permissions $(whoami) "$KNOWN_HOSTS" || failure
-
-    # create a lockfile on known_hosts:
-    lock create "$KNOWN_HOSTS"
-    # FIXME: we're discarding any pre-existing EXIT trap; is this bad?
-    trap "lock remove $KNOWN_HOSTS" EXIT
-
-    # note pre update file checksum
-    fileCheck="$(file_hash "$KNOWN_HOSTS")"
-
-    for host ; do
-       # process the host
-       process_host_known_hosts "$host" || returnCode="$?"
-       # note the result
-       case "$returnCode" in
-           0)
-               nHostsOK=$((nHostsOK+1))
-               ;;
-           2)
-               nHostsBAD=$((nHostsBAD+1))
-               ;;
-       esac
-
-       # touch the lockfile, for good measure.
-       lock touch "$KNOWN_HOSTS"
-    done
-
-    # remove the lockfile and the trap
-    lock remove "$KNOWN_HOSTS"
-    trap - EXIT
-
-    # note if the known_hosts file was updated
-    if [ "$(file_hash "$KNOWN_HOSTS")" != "$fileCheck" ] ; then
-       log debug "known_hosts file updated."
-    fi
-
-    # if an acceptable host was found, return 0
-    if [ "$nHostsOK" -gt 0 ] ; then
-       return 0
-    # else if no ok hosts were found...
-    else
-       # if no bad host were found then no hosts were found at all,
-       # and return 1
-       if [ "$nHostsBAD" -eq 0 ] ; then
-           return 1
-       # else if at least one bad host was found, return 2
-       else
-           return 2
-       fi
-    fi
-}
-
-# process hosts from a known_hosts file
-process_known_hosts() {
-    local hosts
-
-    # exit if the known_hosts file does not exist
-    if [ ! -e "$KNOWN_HOSTS" ] ; then
-       failure "known_hosts file '$KNOWN_HOSTS' does not exist."
-    fi
-
-    log debug "processing known_hosts file:"
-    log debug " $KNOWN_HOSTS"
-
-    hosts=$(meat "$KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | tr , ' ' | tr '\n' ' ')
-
-    if [ -z "$hosts" ] ; then
-       log debug "no hosts to process."
-       return
-    fi
-
-    # take all the hosts from the known_hosts file (first
-    # field), grep out all the hashed hosts (lines starting
-    # with '|')...
-    update_known_hosts $hosts
-}
-
-# process uids for the authorized_keys file
-process_uid_authorized_keys() {
-    local userID
-    local nKeys
-    local nKeysOK
-    local ok
-    local sshKey
-
-    # set the key processing mode
-    export MODE='authorized_keys'
-
-    userID="$1"
-
-    log verbose "processing: $userID"
-
-    nKeys=0
-    nKeysOK=0
+# process an authorized_user_ids file on stdin for authorized_keys
+process_authorized_user_ids() {
+    local authorizedKeys="$1"
+    declare -i nline=0
+    local line
+    declare -a userIDs
+    declare -a koptions
 
+    # extract user IDs from authorized_user_ids file
     IFS=$'\n'
-    for line in $(process_user_id "$userID") ; do
-       # note that key was found
-       nKeys=$((nKeys+1))
-
-       ok=$(echo "$line" | cut -d: -f1)
-       sshKey=$(echo "$line" | cut -d: -f2)
-
-        if [ -z "$sshKey" ] ; then
-            continue
-        fi
-
-       # remove the old host key line
-       remove_line "$AUTHORIZED_KEYS" "$sshKey"
-
-       # if key OK, add new host line
-       if [ "$ok" -eq '0' ] ; then
-           # note that key was found ok
-           nKeysOK=$((nKeysOK+1))
-
-           ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS"
-       fi
-    done
-
-    # if at least one key was found...
-    if [ "$nKeys" -gt 0 ] ; then
-       # if ok keys were found, return 0
-       if [ "$nKeysOK" -gt 0 ] ; then
-           return 0
-       # else return 2
-       else
-           return 2
-       fi
-    # if no keys were found, return 1
-    else
-       return 1
-    fi
-}
-
-# update the authorized_keys files from a list of user IDs on command
-# line
-update_authorized_keys() {
-    local returnCode=0
-    local userID
-    local nIDs
-    local nIDsOK
-    local nIDsBAD
-    local fileCheck
-
-    # the number of ids specified on command line
-    nIDs="$#"
-
-    nIDsOK=0
-    nIDsBAD=0
-
-    log debug "updating authorized_keys file:"
-    log debug " $AUTHORIZED_KEYS"
-
-    # check permissions on the authorized_keys file path
-    check_key_file_permissions $(whoami) "$AUTHORIZED_KEYS" || failure
-
-    # create a lockfile on authorized_keys
-    lock create "$AUTHORIZED_KEYS"
-    # FIXME: we're discarding any pre-existing EXIT trap; is this bad?
-    trap "lock remove $AUTHORIZED_KEYS" EXIT
-
-    # note pre update file checksum
-    fileCheck="$(file_hash "$AUTHORIZED_KEYS")"
-
-    # remove any monkeysphere lines from authorized_keys file
-    remove_monkeysphere_lines "$AUTHORIZED_KEYS"
-
-    for userID ; do
-       # process the user ID, change return code if key not found for
-       # user ID
-       process_uid_authorized_keys "$userID" || returnCode="$?"
-
-       # note the result
-       case "$returnCode" in
-           0)
-               nIDsOK=$((nIDsOK+1))
+    while read line ; do
+       case "$line" in
+           ("#"*)
+               continue
                ;;
-           2)
-               nIDsBAD=$((nIDsBAD+1))
+           (" "*|$'\t'*)
+               if [[ -z ${koptions[${nline}]} ]]; then
+                   koptions[${nline}]=$(echo $line | sed 's/^[         ]*//;s/[        ]$//;')
+               else
+                   koptions[${nline}]="${koptions[${nline}]},$(echo $line | sed 's/^[  ]*//;s/[        ]$//;')"
+               fi
+               ;;
+            (*)
+               ((++nline))
+               userIDs[${nline}]="$line"
+               unset koptions[${nline}] || true
                ;;
        esac
-
-       # touch the lockfile, for good measure.
-       lock touch "$AUTHORIZED_KEYS"
     done
 
-    # remove the lockfile and the trap
-    lock remove "$AUTHORIZED_KEYS"
-
-    # remove the trap
-    trap - EXIT
-
-    # note if the authorized_keys file was updated
-    if [ "$(file_hash "$AUTHORIZED_KEYS")" != "$fileCheck" ] ; then
-       log debug "authorized_keys file updated."
-    fi
-
-    # if an acceptable id was found, return 0
-    if [ "$nIDsOK" -gt 0 ] ; then
-       return 0
-    # else if no ok ids were found...
-    else
-       # if no bad ids were found then no ids were found at all, and
-       # return 1
-       if [ "$nIDsBAD" -eq 0 ] ; then
-           return 1
-       # else if at least one bad id was found, return 2
-       else
-           return 2
-       fi
-    fi
-}
-
-# process an authorized_user_ids file for authorized_keys
-process_authorized_user_ids() {
-    local line
-    local nline
-    local userIDs
-
-    authorizedUserIDs="$1"
-
-    # exit if the authorized_user_ids file is empty
-    if [ ! -e "$authorizedUserIDs" ] ; then
-       failure "authorized_user_ids file '$authorizedUserIDs' does not exist."
-    fi
-
-    log debug "processing authorized_user_ids file:"
-    log debug " $authorizedUserIDs"
-
-    # check permissions on the authorized_user_ids file path
-    check_key_file_permissions $(whoami) "$authorizedUserIDs" || failure
-
-    if ! meat "$authorizedUserIDs" >/dev/null ; then
-       log debug " no user IDs to process."
-       return
-    fi
-
-    nline=0
-
-    # extract user IDs from authorized_user_ids file
-    IFS=$'\n'
-    for line in $(meat "$authorizedUserIDs") ; do
-       userIDs["$nline"]="$line"
-       nline=$((nline+1))
+    for i in $(seq 1 $nline); do
+       AUTHORIZED_KEYS_OPTIONS="${koptions[$i]}" FILE_TYPE='authorized_keys' process_keys_for_file "$authorizedKeys" "${userIDs[$i]}" || returnCode="$?"
     done
-
-    update_authorized_keys "${userIDs[@]}"
 }
 
 # takes a gpg key or keys on stdin, and outputs a list of
 # fingerprints, one per line:
 list_primary_fingerprints() {
     local fake=$(msmktempdir)
-    GNUPGHOME="$fake" gpg --no-tty --quiet --import
+    trap "rm -rf $fake" EXIT
+    GNUPGHOME="$fake" gpg --no-tty --quiet --import --ignore-time-conflict 2>/dev/null
     GNUPGHOME="$fake" gpg --with-colons --fingerprint --list-keys | \
        awk -F: '/^fpr:/{ print $10 }'
+    trap - EXIT
+    rm -rf "$fake"
+}
+
+# takes an OpenPGP key or set of keys on stdin, a fingerprint or other
+# key identifier as $1, and outputs the gpg-formatted information for
+# the requested keys from the material on stdin
+get_cert_info() {
+    local fake=$(msmktempdir)
+    trap "rm -rf $fake" EXIT
+    GNUPGHOME="$fake" gpg --no-tty --quiet --import --ignore-time-conflict 2>/dev/null
+    GNUPGHOME="$fake" gpg --with-colons --fingerprint --fixed-list-mode --list-keys "$1"
+    trap - EXIT
     rm -rf "$fake"
 }