X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Fshare%2Fcommon;h=34ac52da7f5dcfcdd2e893be3234ac400002b18c;hb=90166e0bb8e4ebc1c1174d9bc2021c604b7a1bd7;hp=c88857e44adabca90d57bb02358bcce92d16f67d;hpb=a2704823c7770bc8df9377116877fcca78c3ea12;p=monkeysphere.git diff --git a/src/share/common b/src/share/common index c88857e..34ac52d 100644 --- a/src/share/common +++ b/src/share/common @@ -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 </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" @@ -411,58 +411,16 @@ test_gpg_expire() { 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,12 +436,40 @@ 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`} 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 @@ -492,7 +478,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 @@ -519,13 +505,15 @@ ssh2known_hosts() { # output authorized_keys line from ssh key ssh2authorized_keys() { - local userID - local key - - userID="$1" - key="$2" + local koptions="$1" + local userID="$2" + local key="$3" - printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID" + if [[ -z "$koptions" ]]; then + printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID" + else + printf "%s %s MonkeySphere%s %s\n" "$koptions" "$key" "$DATE" "$userID" + fi } # convert key from gpg to ssh known_hosts format @@ -566,6 +554,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)) @@ -586,6 +583,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" } @@ -595,7 +596,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: @@ -603,15 +604,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 @@ -626,14 +625,8 @@ 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 @@ -716,7 +709,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 @@ -794,6 +787,59 @@ process_user_id() { # being processed in the key files over "bad" keys (key flag '1') } +# output all valid keys for specified user ID literal +keys_for_userid() { + local userID + local noKey= + local nKeys + local nKeysOK + local ok + local sshKey + local tmpfile + + userID="$1" + + log verbose "processing: $userID" + + nKeys=0 + nKeysOK=0 + + 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 + + # if key OK, output key to stdout + if [ "$ok" -eq '0' ] ; then + # note that key was found ok + nKeysOK=$((nKeysOK+1)) + + printf '%s\n' "$sshKey" + 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 +} + # process a single host in the known_host file process_host_known_hosts() { local host @@ -806,7 +852,7 @@ process_host_known_hosts() { local tmpfile # set the key processing mode - export MODE='known_hosts' + export REQUIRED_KEY_CAPABILITY="$REQUIRED_HOST_KEY_CAPABILITY" host="$1" userID="ssh://${host}" @@ -839,13 +885,25 @@ process_host_known_hosts() { # hash if specified if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then + if (type ssh-keygen >/dev/null) ; 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" + 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" + else + # 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. + + failure "Cannot hash known_hosts as requested" + fi else ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS" fi @@ -881,6 +939,7 @@ update_known_hosts() { local nHostsBAD local fileCheck local host + local newUmask # the number of hosts specified on command line nHosts="$#" @@ -890,10 +949,20 @@ update_known_hosts() { # 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") + if [ ! -f "$KNOWN_HOSTS" ]; then + # make sure to create any files or directories with the appropriate write bits turned off: + newUmask=$(printf "%04o" $(( 0$(umask) | 0022 )) ) + [ -d $(dirname "$KNOWN_HOSTS") ] \ + || (umask "$newUmask" && mkdir -p -m 0700 $(dirname "$KNOWN_HOSTS") ) \ + || failure "Could not create path to known_hosts file '$KNOWN_HOSTS'" + # make sure to create this file with the appropriate bits turned off: + (umask "$newUmask" && touch "$KNOWN_HOSTS") \ + || failure "Unable to create known_hosts file '$KNOWN_HOSTS'" + fi # check permissions on the known_hosts file path - check_key_file_permissions $(whoami) "$KNOWN_HOSTS" || failure + check_key_file_permissions $(whoami) "$KNOWN_HOSTS" \ + || failure "Bad permissions governing known_hosts file '$KNOWN_HOSTS'" # create a lockfile on known_hosts: lock create "$KNOWN_HOSTS" @@ -901,7 +970,7 @@ update_known_hosts() { trap "lock remove $KNOWN_HOSTS" EXIT # note pre update file checksum - fileCheck="$(file_hash "$KNOWN_HOSTS")" + fileCheck=$(file_hash "$KNOWN_HOSTS") for host ; do # process the host @@ -973,15 +1042,17 @@ process_known_hosts() { # process uids for the authorized_keys file process_uid_authorized_keys() { local userID + local koptions local nKeys local nKeysOK local ok local sshKey # set the key processing mode - export MODE='authorized_keys' + export REQUIRED_KEY_CAPABILITY="$REQUIRED_USER_KEY_CAPABILITY" - userID="$1" + koptions="$1" + userID="$2" log verbose "processing: $userID" @@ -1008,7 +1079,7 @@ process_uid_authorized_keys() { # note that key was found ok nKeysOK=$((nKeysOK+1)) - ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS" + ssh2authorized_keys "$koptions" "$userID" "$sshKey" >> "$AUTHORIZED_KEYS" fi done @@ -1036,9 +1107,14 @@ update_authorized_keys() { local nIDsOK local nIDsBAD local fileCheck + local x koptions + declare -i argtype + + if (( $# % 2 )); then log error "Bad number of arguments; this should never happen."; return 1; fi # the number of ids specified on command line - nIDs="$#" + (( nIDs=$#/2 )) + (( argtype=0 )) nIDsOK=0 nIDsBAD=0 @@ -1060,23 +1136,30 @@ update_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)) - ;; - 2) - nIDsBAD=$((nIDsBAD+1)) - ;; - esac - - # touch the lockfile, for good measure. - lock touch "$AUTHORIZED_KEYS" + for x; do + (( argtype++ )) + if (( $argtype % 2 )); then + koptions="$x" + else + userID="$x" + + # process the user ID, change return code if key not found + # for user ID + process_uid_authorized_keys "$koptions" "$userID" || returnCode="$?" + + # note the result + case "$returnCode" in + 0) + nIDsOK=$((nIDsOK+1)) + ;; + 2) + nIDsBAD=$((nIDsBAD+1)) + ;; + esac + + # touch the lockfile, for good measure. + lock touch "$AUTHORIZED_KEYS" + fi done # remove the lockfile and the trap @@ -1109,11 +1192,15 @@ update_authorized_keys() { # process an authorized_user_ids file for authorized_keys process_authorized_user_ids() { local line - local nline - local userIDs + declare -i nline + declare -a userIDs + declare -a koptions + declare -a export_array authorizedUserIDs="$1" + (( nline=0 )) + # exit if the authorized_user_ids file is empty if [ ! -e "$authorizedUserIDs" ] ; then failure "authorized_user_ids file '$authorizedUserIDs' does not exist." @@ -1135,20 +1222,50 @@ process_authorized_user_ids() { # extract user IDs from authorized_user_ids file IFS=$'\n' for line in $(meat "$authorizedUserIDs") ; do - userIDs["$nline"]="$line" - nline=$((nline+1)) + case "$line" in + (" "*|$'\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 + done + + for i in $(seq 1 $nline); do + export_array+=("${koptions[$i]}" "${userIDs[$i]}") done - update_authorized_keys "${userIDs[@]}" + update_authorized_keys "${export_array[@]}" } # 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" }