X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Fshare%2Fcommon;h=baf4270c0b4b749b488fd78ec9b3203a03963a7e;hb=658b20d919d3498111efd5a6e901d3ab19d8e669;hp=a21d803b90a83ee34ebe6ceace98ad1e20f7a29a;hpb=adec130f20e374ae0d8f615f45916e27771a0278;p=monkeysphere.git diff --git a/src/share/common b/src/share/common index a21d803..baf4270 100644 --- a/src/share/common +++ b/src/share/common @@ -13,34 +13,14 @@ # all-caps variables are meant to be user supplied (ie. from config # file) and are considered global -######################################################################## -### COMMON VARIABLES - -# managed directories -SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} -export SYSCONFIGDIR - -# monkeysphere version -VERSION=0.23 - -# default log level -LOG_LEVEL="INFO" - -# default keyserver -KEYSERVER="pool.sks-keyservers.net" - -# whether or not to check keyservers by defaul -CHECK_KEYSERVER="true" - -# default monkeysphere user -MONKEYSPHERE_USER="monkeysphere" - -# default about whether or not to prompt -PROMPT="true" - ######################################################################## ### UTILITY FUNCTIONS +# output version info +version() { + cat "${SYSSHAREDIR}/VERSION" +} + # failure function. exits with code 255, unless specified otherwise. failure() { [ "$1" ] && echo "$1" >&2 @@ -96,11 +76,10 @@ log() { fi if [ "$priority" = "$level" -a "$output" = 'true' ] ; then if [ "$1" ] ; then - echo -n "ms: " >&2 - echo "$@" >&2 + echo "$@" else - cat >&2 - fi + cat + fi | sed 's/^/'"${LOG_PREFIX}"'/' >&2 fi done } @@ -124,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 @@ -168,8 +147,8 @@ lock() { local action="$1" local file="$2" - if ! ( which lockfile-create >/dev/null 2>/dev/null ) ; then - if ! ( which lockfile >/dev/null ); then + 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= @@ -218,7 +197,7 @@ advance_date() { local shortunits # try things the GNU way first - if date -d "$number $longunits" "$format" >/dev/null 2>&1; then + 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: @@ -273,7 +252,13 @@ check_capability() { # hash of a file file_hash() { - md5sum "$1" 2> /dev/null + 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 @@ -296,7 +281,7 @@ get_gpg_expiration() { keyExpire="$1" - if [ -z "$keyExpire" -a "$PROMPT" = 'true' ]; then + if [ -z "$keyExpire" -a "$PROMPT" != 'false' ]; then cat >&2 <y = key expires in n years EOF while [ -z "$keyExpire" ] ; do - read -p "Key is valid for? (0) " keyExpire + printf "Key is valid for? (0) " >&2 + read keyExpire if ! test_gpg_expire ${keyExpire:=0} ; then echo "invalid value" >&2 unset keyExpire @@ -324,7 +310,9 @@ passphrase_prompt() { local fifo="$2" local PASS - if [ "$DISPLAY" ] && which "${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" else read -s -p "$prompt" PASS @@ -334,107 +322,38 @@ passphrase_prompt() { fi } -test_gnu_dummy_s2k_extension() { - -# this block contains a demonstration private key that has had the -# primary key stripped out using the GNU S2K extension known as -# "gnu-dummy" (see /usr/share/doc/gnupg/DETAILS.gz). The subkey is -# present in cleartext, however. - -# openpgp2ssh will be able to deal with this based on whether the -# local copy of GnuTLS contains read_s2k support that can handle it. - -# read up on that here: - -# http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html - -echo " ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.9 (GNU/Linux) - -lQCVBEO3YdABBACRqqEnucag4+vyZny2M67Pai5+5suIRRvY+Ly8Ms5MvgCi3EVV -xT05O/+0ShiRaf+QicCOFrhbU9PZzzU+seEvkeW2UCu4dQfILkmj+HBEIltGnHr3 -G0yegHj5pnqrcezERURf2e17gGFWX91cXB9Cm721FPXczuKraphKwCA9PwARAQAB -/gNlAkdOVQG0OURlbW9uc3RyYXRpb24gS2V5IGZvciBTMksgR05VIGV4dGVuc2lv -biAxMDAxIC0tIGdudS1kdW1teYi8BBMBAgAmBQJDt2HQAhsDBQkB4TOABgsJCAcD -AgQVAggDBBYCAwECHgECF4AACgkQQZUwSa4UDezTOQP/TMQXUVrWzHYZGopoPZ2+ -ZS3qddiznBHsgb7MGYg1KlTiVJSroDUBCHIUJvdQKZV9zrzrFl47D07x6hGyUPHV -aZXvuITW8t1o5MMHkCy3pmJ2KgfDvdUxrBvLfgPMICA4c6zA0mWquee43syEW9NY -g3q61iPlQwD1J1kX1wlimLCdAdgEQ7dh0AEEANAwa63zlQbuy1Meliy8otwiOa+a -mH6pxxUgUNggjyjO5qx+rl25mMjvGIRX4/L1QwIBXJBVi3SgvJW1COZxZqBYqj9U -8HVT07mWKFEDf0rZLeUE2jTm16cF9fcW4DQhW+sfYm+hi2sY3HeMuwlUBK9KHfW2 -+bGeDzVZ4pqfUEudABEBAAEAA/0bemib+wxub9IyVFUp7nPobjQC83qxLSNzrGI/ -RHzgu/5CQi4tfLOnwbcQsLELfker2hYnjsLrT9PURqK4F7udrWEoZ1I1LymOtLG/ -4tNZ7Mnul3wRC2tCn7FKx8sGJwGh/3li8vZ6ALVJAyOia5TZ/buX0+QZzt6+hPKk -7MU1WQIA4bUBjtrsqDwro94DvPj3/jBnMZbXr6WZIItLNeVDUcM8oHL807Am97K1 -ueO/f6v1sGAHG6lVPTmtekqPSTWBfwIA7CGFvEyvSALfB8NUa6jtk27NCiw0csql -kuhCmwXGMVOiryKEfegkIahf2bAd/gnWHPrpWp7bUE20v8YoW22I4wIAhnm5Wr5Q -Sy7EHDUxmJm5TzadFp9gq08qNzHBpXSYXXJ3JuWcL1/awUqp3tE1I6zZ0hZ38Ia6 -SdBMN88idnhDPqPoiKUEGAECAA8FAkO3YdACGyAFCQHhM4AACgkQQZUwSa4UDezm -vQP/ZhK+2ly9oI2z7ZcNC/BJRch0/ybQ3haahII8pXXmOThpZohr/LUgoWgCZdXg -vP6yiszNk2tIs8KphCAw7Lw/qzDC2hEORjWO4f46qk73RAgSqG/GyzI4ltWiDhqn -vnQCFl3+QFSe4zinqykHnLwGPMXv428d/ZjkIc2ju8dRsn4= -=CR5w ------END PGP PRIVATE KEY BLOCK----- -" | openpgp2ssh 4129E89D17C1D591 >/dev/null 2>/dev/null - -} - # 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" - - if [ -z "$file" ] ; then - return 1 - fi - - if [ ! -e "$file" ] ; then - return 1 - 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 @@ -446,7 +365,7 @@ translate_ssh_variables() { path="$2" # get the user's home directory - userHome=$(getent passwd "$uname" | cut -d: -f6) + userHome=$(get_homedir "$uname") # translate '%u' to user name path=${path/\%u/"$uname"} @@ -461,54 +380,87 @@ 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" + if [ "$STRICT_MODES" = 'false' ] ; then + log debug "skipping path permission check for '$path' because STRICT_MODES is false..." + return 0 + fi log debug "checking path permission '$path'..." + "${SYSSHAREDIR}/checkperms" "$uname" "$path" +} - # return 255 if cannot stat file - if ! stat=$(ls -ld "$path" 2>/dev/null) ; then - log error "could not stat path '$path'." - return 255 +# 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 +} - 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'." - return 1 +# 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 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'." - return 2 - fi +# return the path to the home directory of a user +get_homedir() { + local uname=${1:-`whoami`} + eval "echo ~${uname}" +} - # return zero if all clear, or go to next path - if [ "$path" = '/' ] ; then - return 0 - else - check_key_file_permissions "$uname" $(dirname "$path") - fi +# return the primary group of a user +get_primary_group() { + local uname=${1:-`whoami`} + groups "$uname" | sed 's/^..* : //' | awk '{ print $1 }' } ### CONVERSION UTILITIES @@ -519,69 +471,119 @@ 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 ssh2known_hosts() { local host + local port local key - host="$1" + # 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" - echo -n "$host " - echo -n "$key" | tr -d '\n' - echo " MonkeySphere${DATE}" + # specify the host and port properly for new ssh known_hosts + # format + if [ "$port" != "$host" ] ; then + host="[${host}]:${port}" + fi + + # 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" - echo -n "$key" | tr -d '\n' - echo " MonkeySphere${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 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}$' - echo -n "$host " - gpg2ssh "$keyID" | tr -d '\n' - echo " MonkeySphere${DATE}" + 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}$' - gpg2ssh "$keyID" | tr -d '\n' - echo " MonkeySphere${DATE} ${userID}" + printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID" } ### 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)) @@ -599,9 +601,13 @@ gpg_fetch_userid() { echo 1,2,3,4,5 | \ gpg --quiet --batch --with-colons \ --command-fd 0 --keyserver "$KEYSERVER" \ - --search ="$userID" > /dev/null 2>&1 + --search ="$userID" &>/dev/null returnCode="$?" + if [ "$returnCode" != 0 ] ; then + log error "Failure ($returnCode) searching keyserver $KEYSERVER for user id '$userID'" + fi + return "$returnCode" } @@ -611,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: @@ -619,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 @@ -642,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="$?" @@ -732,14 +730,14 @@ 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 or DSA?)." + log verbose " ! 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 or DSA?)." + log debug " ! primary key could not be translated (not RSA?)." else echo "1:${sshKey}" fi @@ -790,14 +788,14 @@ process_user_id() { if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then log verbose " * acceptable sub key." if [ -z "$sshKey" ] ; then - log error " ! sub key could not be translated (not RSA or DSA?)." + log error " ! sub key could not be translated (not RSA?)." else echo "0:${sshKey}" fi else log debug " - unacceptable sub key." if [ -z "$sshKey" ] ; then - log debug " ! sub key could not be translated (not RSA or DSA?)." + log debug " ! sub key could not be translated (not RSA?)." else echo "1:${sshKey}" fi @@ -810,356 +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 + + ((++KEYS_PROCESSED)) - # if key OK, add new host line + # 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 -} - -# 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 "$USER" "$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 + log debug "KEYS_PROCESSED=$KEYS_PROCESSED" + log debug "KEYS_VALID=$KEYS_VALID" } -# 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..." - - 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 - - # check permissions on the authorized_keys file path - check_key_file_permissions "$USER" "$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 - - # check permissions on the authorized_user_ids file path - check_key_file_permissions "$USER" "$authorizedUserIDs" || failure - - log debug "processing authorized_user_ids file..." - - 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" }