New client/server components:
authorJameson Graef Rollins <jrollins@phys.columbia.edu>
Tue, 10 Jun 2008 21:17:51 +0000 (17:17 -0400)
committerJameson Graef Rollins <jrollins@phys.columbia.edu>
Tue, 10 Jun 2008 21:17:51 +0000 (17:17 -0400)
- broke out all common functions to "common" file
- put all client commands into "monkeysphere" script
- put all server commands into "monkeysphere-server" script
- moved all code into src directory to clean things up a bit
- this effectively makes obsolete rhesus and howler
- added proposed monkeysphere-ssh-proxycommand script that can be
  called to update known_hosts from ssh ProxyCommand
- updated monkeysphere.conf to work as global client config
- added monkeysphere-server.conf for server config

17 files changed:
.gitignore
langur/README [deleted file]
monkeysphere-server.conf [new file with mode: 0644]
monkeysphere.conf
src/common [new file with mode: 0755]
src/gpg2ssh/Makefile [moved from gpg2ssh/Makefile with 100% similarity]
src/gpg2ssh/gnutls-helpers.c [moved from gpg2ssh/gnutls-helpers.c with 100% similarity]
src/gpg2ssh/gnutls-helpers.h [moved from gpg2ssh/gnutls-helpers.h with 100% similarity]
src/gpg2ssh/gpg2ssh.c [moved from gpg2ssh/gpg2ssh.c with 100% similarity]
src/gpg2ssh/main.c [moved from gpg2ssh/main.c with 100% similarity]
src/gpg2ssh/ssh2gpg.c [moved from gpg2ssh/ssh2gpg.c with 100% similarity]
src/howler/howler [moved from howler/howler with 100% similarity]
src/monkeysphere [new file with mode: 0755]
src/monkeysphere-server [new file with mode: 0755]
src/monkeysphere-ssh-proxycommand [new file with mode: 0755]
src/rhesus/README [moved from rhesus/README with 100% similarity]
src/rhesus/rhesus [moved from rhesus/rhesus with 100% similarity]

index 80bf65d079a0fd09d8b7292fd3b2846f1ab16765..0dc4f7940b1522b90094938ac7d640c7a74603ad 100644 (file)
@@ -1,5 +1,2 @@
 *~
 *.[ao]
-monkeysphere
-gpg2ssh
-ssh2gpg
diff --git a/langur/README b/langur/README
deleted file mode 100644 (file)
index ee60701..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Langur is the policy editor/viewer for the monkeysphere.
-
-Its goals are to provide a human-friendly interface to the simple and
-intelligible policies monkeysphere supports.
diff --git a/monkeysphere-server.conf b/monkeysphere-server.conf
new file mode 100644 (file)
index 0000000..bed5c09
--- /dev/null
@@ -0,0 +1,23 @@
+# MonkeySphere server configuration file.
+
+# GPG home directory for server
+#GNUPGHOME=/etc/monkeysphere/gnupg
+
+# GPG keyserver to search for keys
+#KEYSERVER=subkeys.pgp.net
+
+# Required key capabilities
+# Must be quoted, lowercase, space-seperated list of the following:
+#   e = encrypt
+#   s = sign
+#   c = certify
+#   a = authentication
+#REQUIRED_KEY_CAPABILITY="e a"
+
+# Whether to add user controlled authorized_keys file to
+# monkeysphere-generated authorized_keys file.  Should be path to file
+# where '%h' will be substituted for the user's home directory.
+#USER_CONTROLLED_AUTHORIZED_KEYS=%h/.ssh/authorized_keys
+
+# where to cache user authorized_keys lines
+#STAGING_AREA=/var/lib/monkeysphere/stage
index 640120382d75d1cbee31882b49562a6328f7331b..385165a28a83e99e1d224999a747e9cc7029a76e 100644 (file)
@@ -1,30 +1,31 @@
-# monkeysphere system configuration file
+# MonkeySphere system-wide client configuration file.
 
-# This is particular configuration is meant to be sourced by the
-# rhesus shell script when run in administrative mode to maintain
-# authorized_keys files for users.
+# authorized_user_ids file
+#AUTHORIZED_USER_IDS=~/.config/monkeysphere/authorized_user_ids
 
-AUTHORIZED_USER_IDS=/etc/monkeysphere/authorized_user_ids/"$USER"
+# GPG home directory
+#GNUPGHOME=~/.gnupg
 
-STAGING_AREA=/var/lib/monkeysphere/stage/"$USER"
+# GPG keyserver to search for keys
+#KEYSERVER=subkeys.pgp.net
 
-# gpg home directory for server
-GNUPGHOME=/etc/monkeysphere/gnupg
-
-# gpg keyserver to search for keys
-KEYSERVER=subkeys.pgp.net
-
-# required capabilities of keys
-# must be quoted, lowercase, space-seperated list of the following:
+# Required key capabilities
+# Must be quoted, lowercase, space-seperated list of the following:
 #   e = encrypt
 #   s = sign
 #   c = certify
 #   a = authentication
-REQUIRED_KEY_CAPABILITY="e a"
+#REQUIRED_KEY_CAPABILITY="e a"
 
 # Path to user-controlled authorized_keys file to add to
 # Monkeysphere-generated authorized_keys file. If empty, then no
-# user-controlled file will be added.  To specify the user's home
-# directory, use the string "~${USER}"
-USER_CONTROLLED_AUTHORIZED_KEYS="~${USER}/.ssh/authorized_keys"
+# user-controlled file will be added.
+#USER_CONTROLLED_AUTHORIZED_KEYS=~/.ssh/authorized_keys
+
+# User known_hosts file
+#USER_KNOWN_HOSTS=~/.ssh/known_hosts
+
+# Whether or not to hash the generated known_hosts lines
+# (empty mean "no").
+#HASH_KNOWN_HOSTS=
 
diff --git a/src/common b/src/common
new file mode 100755 (executable)
index 0000000..8643080
--- /dev/null
@@ -0,0 +1,353 @@
+# -*-shell-script-*-
+
+# Shared bash functions for the monkeysphere
+#
+# Written by
+# Jameson Rollins <jrollins@fifthhorseman.net>
+#
+# Copyright 2008, released under the GPL, version 3 or later
+
+# all caps variables are meant to be user supplied (ie. from config
+# file) and are considered global
+
+########################################################################
+# managed directories
+ETC="/etc/monkeysphere"
+export ETC
+LIB="/var/lib/monkeysphere"
+export LIB 
+########################################################################
+
+failure() {
+    echo "$1" >&2
+    exit ${2:-'1'}
+}
+
+# write output to stdout
+log() {
+    echo -n "ms: "
+    echo "$@"
+}
+
+# write output to stderr
+loge() {
+    echo -n "ms: " 1>&2
+    echo "$@" 1>&2
+}
+
+# cut out all comments(#) and blank lines from standard input
+meat() {
+    grep -v -e "^[[:space:]]*#" -e '^$'
+}
+
+# cut a specified line from standard input
+cutline() {
+    head --line="$1" | tail -1
+}
+
+# retrieve all keys with given user id from keyserver
+# FIXME: need to figure out how to retrieve all matching keys
+# (not just first 5)
+gpg_fetch_keys() {
+    local id
+    id="$1"
+    echo 1,2,3,4,5 | \
+       gpg --quiet --batch --command-fd 0 --with-colons \
+       --keyserver "$KEYSERVER" \
+       --search ="$id" >/dev/null 2>&1
+}
+
+# check that characters are in a string (in an AND fashion).
+# used for checking key capability
+# check_capability capability a [b...]
+check_capability() {
+    local capability
+    local capcheck
+
+    capability="$1"
+    shift 1
+
+    for capcheck ; do
+       if echo "$capability" | grep -q -v "$capcheck" ; then
+           return 1
+       fi
+    done
+    return 0
+}
+
+# convert escaped characters from gpg output back into original
+# character
+# FIXME: undo all escape character translation in with-colons gpg output
+unescape() {
+    echo "$1" | sed 's/\\x3a/:/'
+}
+
+# stand in until we get dkg's gpg2ssh program
+gpg2ssh_tmp() {
+    local keyID
+    local userID
+    local host
+
+    keyID="$2"
+    userID="$3"
+
+    if [ "$mode" = 'authorized_keys' ] ; then
+       gpgkey2ssh "$keyID" | sed -e "s/COMMENT/${userID}/"
+
+    # 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}$'
+    elif [ "$MODE" = 'known_hosts' ] ; then
+       host=$(echo "$userID" | sed -e "s|ssh://||")
+       echo -n "$host "; gpgkey2ssh "$keyID" | sed -e "s/COMMENT/MonkeySphere${DATE}/"
+    fi
+}
+
+# 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 particular desired user id has appropriate validity
+# see /usr/share/doc/gnupg/DETAILS.gz
+# expects global variable: "MODE"
+process_user_id() {
+    local userID
+    local cacheDir
+    local requiredPubCapability
+    local gpgOut
+    local line
+    local type
+    local validity
+    local keyid
+    local uidfpr
+    local capability
+    local keyOK
+    local pubKeyID
+    local uidOK
+    local keyIDs
+    local userIDHash
+    local keyID
+
+    userID="$1"
+    cacheDir="$2"
+
+    requiredPubCapability=$(echo "$REQUIRED_KEY_CAPABILITY" | tr "[:lower:]" "[:upper:]")
+
+    # fetch keys from keyserver, return 1 if none found
+    gpg_fetch_keys "$userID" || return 1
+
+    # output gpg info for (exact) userid and store
+    gpgOut=$(gpg --fixed-list-mode --list-key --with-colons \
+       ="$userID" 2> /dev/null)
+
+    # return 1 if there only "tru" lines are output from gpg
+    if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then
+       loge "  key not found."
+       return 1
+    fi
+
+    # loop over all lines in the gpg output and process.
+    # need to do it this way (as opposed to "while read...") so that
+    # variables set in loop will be visible outside of loop
+    for line in $(seq 1 $(echo "$gpgOut" | wc -l)) ; do
+
+       # read the contents of the line
+       type=$(echo "$gpgOut" | cutline "$line" | cut -d: -f1)
+       validity=$(echo "$gpgOut" | cutline "$line" | cut -d: -f2)
+       keyid=$(echo "$gpgOut" | cutline "$line" | cut -d: -f5)
+       uidfpr=$(echo "$gpgOut" | cutline "$line" | cut -d: -f10)
+       capability=$(echo "$gpgOut" | cutline "$line" | cut -d: -f12)
+
+       # process based on record type
+       case $type in
+           'pub') # primary keys
+               # new key, wipe the slate
+               keyOK=
+               pubKeyID=
+               uidOK=
+               keyIDs=
+
+               pubKeyID="$keyid"
+
+               # check primary key validity
+               if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
+                   loge "  unacceptable primary key validity ($validity)."
+                   continue
+               fi
+               # check capability is not Disabled...
+               if check_capability "$capability" 'D' ; then
+                   loge "  key disabled."
+                   continue
+               fi
+               # check overall key capability
+               # must be Encryption and Authentication
+               if ! check_capability "$capability" $requiredPubCapability ; then
+                   loge "  unacceptable primary key capability ($capability)."
+                   continue
+               fi
+
+               # mark if primary key is acceptable
+               keyOK=true
+
+               # add primary key ID to key list if it has required capability
+               if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
+                   keyIDs[${#keyIDs[*]}]="$keyid"
+               fi
+               ;;
+           'uid') # user ids
+               # check key ok and we have key fingerprint
+               if [ -z "$keyOK" ] ; then
+                   continue
+               fi
+               # check key validity
+               if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
+                   continue
+               fi
+               # check the uid matches
+               if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
+                   continue
+               fi
+
+               # mark if uid acceptable
+               uidOK=true
+               ;;
+           'sub') # sub keys
+               # add sub key ID to key list if it has required capability
+               if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
+                   keyIDs[${#keyIDs[*]}]="$keyid"
+               fi
+               ;;
+       esac
+    done
+
+    # hash userid for cache file name
+    userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }')
+
+    # touch/clear key cache file
+    # (will be left empty if there are noacceptable keys)
+    > "$cacheDir"/"$userIDHash"."$pubKeyID"
+
+    # for each acceptable key, write an ssh key line to the
+    # key cache file
+    if [ "$keyOK" -a "$uidOK" -a "${keyIDs[*]}" ] ; then
+       for keyID in ${keyIDs[@]} ; do
+           loge "  acceptable key/uid found."
+
+           # export the key with gpg2ssh
+            # FIXME: needs to apply extra options for authorized_keys
+           # lines if specified
+           gpg2ssh_tmp "$keyID" "$userID" >> "$cacheDir"/"$userIDHash"."$pubKeyID"
+
+           # hash the cache file if specified
+           if [ "$MODE" = 'known_hosts' -a "$HASH_KNOWN_HOSTS" ] ; then
+               ssh-keygen -H -f "$cacheDir"/"$userIDHash"."$pubKeyID" > /dev/null 2>&1
+               rm "$cacheDir"/"$userIDHash"."$pubKeyID".old
+           fi
+       done
+    fi
+
+    # echo the path to the key cache file
+    echo "$cacheDir"/"$userIDHash"."$pubKeyID"
+}
+
+# process a host for addition to a known_host file
+process_host() {
+    local host
+    local cacheDir
+    local hostKeyCachePath
+
+    host="$1"
+    cacheDir="$2"
+
+    log "processing host: '$host'"
+
+    hostKeyCachePath=$(process_user_id "ssh://${host}" "$cacheDir")
+    if [ $? = 0 ] ; then
+       ssh-keygen -R "$host" -f "$USER_KNOWN_HOSTS"
+       cat "$hostKeyCachePath" >> "$USER_KNOWN_HOSTS"
+    fi
+}
+
+# process known_hosts file
+# go through line-by-line, extract each host, and process with the
+# host processing function
+process_known_hosts() {
+    local knownHosts
+    local cacheDir
+    local hosts
+    local host
+
+    knownHosts="$1"
+    cacheDir="$2"
+
+    # take all the hosts from the known_hosts file (first field),
+    # grep out all the hashed hosts (lines starting with '|')
+    cut -d ' ' -f 1 "$knownHosts" | \
+    grep -v '^|.*$' | \
+    while IFS=, read -r -a hosts ; do
+       # process each host
+       for host in ${hosts[*]} ; do
+           process_host "$host" "$cacheDir"
+       done
+    done
+}
+
+# process authorized_keys file
+# go through line-by-line, extract monkeysphere userids from comment
+# fields, and process each userid
+process_authorized_keys() {
+    local authorizedKeys
+    local cacheDir
+    local userID
+
+    authorizedKeys="$1"
+    cacheDir="$2"
+
+    # take all the monkeysphere userids from the authorized_keys file
+    # comment field (third field) that starts with "MonkeySphere uid:"
+    # FIXME: needs to handle authorized_keys options (field 0)
+    cat "$authorizedKeys" | \
+    while read -r options keytype key comment ; do
+       # if the comment field is empty, assume the third field was
+       # the comment
+       if [ -z "$comment" ] ; then
+           comment="$key"
+       fi
+       if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then
+           continue
+       fi
+       userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://")
+       if [ -z "$userID" ] ; then
+           continue
+       fi
+       # process the userid
+       log "processing userid: '$userID'"
+       process_user_id "$userID" "$cacheDir" > /dev/null
+    done
+}
+
+# process an authorized_*_ids file
+# go through line-by-line, extract each userid, and process
+process_authorized_ids() {
+    local authorizedIDs
+    local cacheDir
+    local userID
+
+    authorizedIDs="$1"
+    cacheDir="$2"
+
+    # clean out keys file and remake keys directory
+    rm -rf "$cacheDir"
+    mkdir -p "$cacheDir"
+
+    # loop through all user ids in file
+    # FIXME: needs to handle authorized_keys options
+    cat "$authorizedIDs" | meat | \
+    while read -r userID ; do
+       # process the userid
+       log "processing userid: '$userID'"
+       process_user_id "$userID" "$cacheDir" > /dev/null
+    done
+}
similarity index 100%
rename from gpg2ssh/Makefile
rename to src/gpg2ssh/Makefile
similarity index 100%
rename from gpg2ssh/gpg2ssh.c
rename to src/gpg2ssh/gpg2ssh.c
similarity index 100%
rename from gpg2ssh/main.c
rename to src/gpg2ssh/main.c
similarity index 100%
rename from gpg2ssh/ssh2gpg.c
rename to src/gpg2ssh/ssh2gpg.c
similarity index 100%
rename from howler/howler
rename to src/howler/howler
diff --git a/src/monkeysphere b/src/monkeysphere
new file mode 100755 (executable)
index 0000000..f279d86
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+########################################################################
+PGRM=$(basename $0)
+
+SHAREDIR=${SHAREDIR:-"/usr/share/monkeysphere"}
+export SHAREDIR
+. "${SHAREDIR}/common"
+
+GLOBAL_CONFIG=${GLOBAL_CONFIG:-"${ETC}"/monkeysphere.conf}
+[ -r "$GLOBAL_CONFIG" ] && . "$GLOBAL_CONFIG"
+
+# date in UTF format if needed
+DATE=$(date -u '+%FT%T')
+
+# unset some environment variables that could screw things up
+GREP_OPTIONS=
+
+########################################################################
+# FUNCTIONS
+########################################################################
+
+usage() {
+cat <<EOF
+usage: $PGRM <subcommand> [args]
+Monkeysphere client tool.
+
+subcommands:
+  update-known-hosts (k) [HOST]...           update known_hosts file
+  update-authorized-keys (a)                 update authorized_keys file
+  update-userid (u) [USERID]...              add/update userid to
+                                               authorized_user_ids
+  help (h,?)                                 this help
+
+EOF
+}
+
+########################################################################
+# MAIN
+########################################################################
+
+COMMAND="$1"
+[ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
+shift
+
+# set ms home directory
+MS_HOME=${MS_HOME:-"$HOME"/.config/monkeysphere}
+
+# load configuration file
+MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf}
+[ -e "$MS_CONF" ] && . "$MS_CONF"
+
+# set empty config variable with defaults
+AUTHORIZED_USER_IDS=${AUTHORIZED_USER_IDS:-"$MS_HOME"/authorized_user_ids}
+GNUPGHOME=${GNUPGHOME:-"$HOME"/.gnupg}
+KEYSERVER=${KEYSERVER:-subkeys.pgp.net}
+REQUIRED_KEY_CAPABILITY=${REQUIRED_KEY_CAPABILITY:-"e a"}
+USER_CONTROLLED_AUTHORIZED_KEYS=${USER_CONTROLLED_AUTHORIZED_KEYS:-%h/.ssh/authorized_keys}
+USER_KNOWN_HOSTS=${USER_KNOWN_HOSTS:-"$HOME"/.ssh/known_hosts}
+HASH_KNOWN_HOSTS=${HASH_KNOWN_HOSTS:-}
+
+export GNUPGHOME
+
+# stagging locations
+hostKeysCacheDir="$MS_HOME"/host_keys
+userKeysCacheDir="$MS_HOME"/user_keys
+msAuthorizedKeys="$MS_HOME"/authorized_keys
+
+# make sure gpg home exists with proper permissions
+mkdir -p -m 0700 "$GNUPGHOME"
+
+case $COMMAND in
+    'update-known-hosts'|'k')
+       MODE='known_hosts'
+
+        # touch the known_hosts file to make sure it exists
+       touch "$USER_KNOWN_HOSTS"
+
+        # if hosts are specified on the command line, process just
+        # those hosts
+       if [ "$1" ] ; then
+            for host ; do
+               process_host "$host" "$hostKeysCacheDir"
+           done
+
+        # otherwise, if no hosts are specified, process the user
+        # known_hosts file
+       else
+           if [ ! -s "$USER_KNOWN_HOSTS" ] ; then
+               failure "known_hosts file '$USER_KNOWN_HOSTS' is empty."
+           fi
+           log "processing known_hosts file..."
+           process_known_hosts "$USER_KNOWN_HOSTS" "$hostKeysCacheDir"
+       fi
+       ;;
+
+    'update-authorized-keys'|'a')
+       MODE='authorized_keys'
+
+       log "processing authorized_user_ids file..."
+
+        # make sure authorized_user_ids file exists
+       if [ ! -s "$AUTHORIZED_USER_IDS" ] ; then
+           log "authorized_user_ids file is empty or does not exist."
+           exit
+       fi
+
+       process_authorized_ids "$AUTHORIZED_USER_IDS" "$userKeysCacheDir"
+
+        # write output key file
+       log "writing monkeysphere authorized_keys file... "
+       touch "$msAuthorizedKeys"
+       if [ "$(ls "$userKeysCacheDir")" ] ; then
+           log -n "adding gpg keys... "
+           cat "$userKeysCacheDir"/* > "$msAuthorizedKeys"
+           echo "done."
+       else
+       log "no gpg keys to add."
+       fi
+       if [ "$USER_CONTROLLED_AUTHORIZED_KEYS" ] ; then
+           userAuthorizedKeys=${USER_CONTROLLED_AUTHORIZED_KEYS/\%h/"$HOME"}
+           if [ -s "$userAuthorizedKeys" ] ; then
+               log -n "adding user authorized_keys file... "
+               cat "$userAuthorizedKeys" >> "$msAuthorizedKeys"
+               echo "done."
+           fi
+       fi
+       log "monkeysphere authorized_keys file generated:"
+       log "$msAuthorizedKeys"
+       ;;
+
+    'update-userid'|'u')
+       if [ -z "$1" ] ; then
+           failure "you must specify at least one userid."
+       fi
+       for userID ; do
+           if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
+               log "userid '$userID' not in authorized_user_ids file."
+               continue
+           fi
+           log "processing user id: '$userID'"
+           process_user_id "$userID" "$userKeysCacheDir" > /dev/null
+       done
+       ;;
+
+    'help'|'h'|'?')
+        usage
+        ;;
+
+    *)
+        failure "Unknown command: '$COMMAND'
+Type 'cereal-admin help' for usage."
+        ;;
+esac
diff --git a/src/monkeysphere-server b/src/monkeysphere-server
new file mode 100755 (executable)
index 0000000..f1b4892
--- /dev/null
@@ -0,0 +1,219 @@
+#!/bin/sh
+
+########################################################################
+PGRM=$(basename $0)
+
+SHAREDIR=${SHAREDIR:-"/usr/share/monkeysphere"}
+export SHAREDIR
+. "${SHAREDIR}/common"
+
+# date in UTF format if needed
+DATE=$(date -u '+%FT%T')
+
+# unset some environment variables that could screw things up
+GREP_OPTIONS=
+
+########################################################################
+# FUNCTIONS
+########################################################################
+
+usage() {
+cat <<EOF
+usage: $PGRM <subcommand> [args]
+Monkeysphere server admin tool.
+
+subcommands:
+  update-users (s) [USER]...                update authorized_keys file
+  gen-key (g)                               generate gpg key for the host
+  publish-key (p)                           publish host gpg to keyserver
+  trust-key (t) KEYID [KEYID]...            mark keyid as trusted
+  update-user-userid (u) USER UID [UID]...  add/update userid for user
+  help (h,?)                                this help
+
+EOF
+}
+
+# generate server gpg key
+gen_key() {
+    KEY_TYPE=${KEY_TYPE:-RSA}
+    KEY_LENGTH=${KEY_LENGTH:-2048}
+    KEY_USAGE=${KEY_USAGE:-encrypt,auth}
+    SERVICE=${SERVICE:-ssh}
+    HOSTNAME_FQDN=${HOSTNAME_FQDN:-$(hostname -f)}
+
+    USERID=${USERID:-"$SERVICE"://"$HOSTNAME_FQDN"}
+
+    echo "key parameters:"
+    cat <<EOF
+Key-Type: $KEY_TYPE
+Key-Length: $KEY_LENGTH
+Key-Usage: $KEY_USAGE
+Name-Real: $USERID
+EOF
+
+    read -p "generate key? [Y|n]: " OK; OK=${OK:=Y}
+    if [ ${OK/y/Y} != 'Y' ] ; then
+       failure "aborting."
+    fi
+
+    if gpg --list-key ="$USERID" > /dev/null 2>&1 ; then
+       failure "key for '$USERID' already exists"
+    fi
+
+    echo "generating server key..."
+    gpg --batch --gen-key <<EOF
+Key-Type: $KEY_TYPE
+Key-Length: $KEY_LENGTH
+Key-Usage: $KEY_USAGE
+Name-Real: $USERID
+%commit
+EOF
+}
+
+# publish server key to keyserver
+publish_key() {
+    read -p "publish key to $KEYSERVER? [Y|n]: " OK; OK=${OK:=Y}
+    if [ ${OK/y/Y} != 'Y' ] ; then
+       failure "aborting."
+    fi
+
+    keyID=$(gpg --list-key --with-colons ="$USERID" 2> /dev/null | grep '^pub:' | cut -d: -f5)
+
+    # dummy command so as not to publish fakes keys during testing
+    # eventually:
+    #gpg --send-keys --keyserver "$KEYSERVER" "$keyID"
+    echo "gpg --send-keys --keyserver $KEYSERVER $keyID"
+}
+
+# trust key
+trust_key() {
+    for keyID ; do
+       # get the key from the key server
+       gpg --keyserver "$KEYSERVER" --recv-key "$keyID" || failure "could not retrieve key '$keyID'"
+
+       # edit the key to change trust
+       # FIXME: need to figure out how to automate this,
+       # in a batch mode or something.
+       gpg --edit-key "$keyID"
+    done
+}
+
+########################################################################
+# MAIN
+########################################################################
+
+COMMAND="$1"
+[ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
+shift
+
+# set ms home directory
+MS_HOME=${MS_HOME:-"$ETC"}
+
+# load configuration file
+MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere-server.conf}
+[ -e "$MS_CONF" ] && . "$MS_CONF"
+
+# set empty config variable with defaults
+GNUPGHOME=${GNUPGHOME:-"$MS_HOME"/gnupg}
+KEYSERVER=${KEYSERVER:-subkeys.pgp.net}
+REQUIRED_KEY_CAPABILITY=${REQUIRED_KEY_CAPABILITY:-"e a"}
+USER_CONTROLLED_AUTHORIZED_KEYS=${USER_CONTROLLED_AUTHORIZED_KEYS:-%h/.ssh/authorized_keys}
+STAGING_AREA=${STAGING_AREA:-"$LIB"/stage}
+
+export GNUPGHOME
+
+# make sure gpg home exists with proper permissions
+mkdir -p -m 0700 "$GNUPGHOME"
+
+case $COMMAND in
+    'update-users'|'s')
+       if [ "$1" ] ; then
+           unames="$@"
+       else
+           unames=$(ls -1 "$MS_HOME"/authorized_user_ids)
+       fi
+
+       for uname in $unames ; do
+           MODE="authorized_keys"
+           authorizedUserIDs="$MS_HOME"/authorized_user_ids/"$uname"
+           cacheDir="$STAGING_AREA"/"$uname"/user_keys
+           msAuthorizedKeys="$STAGING_AREA"/"$uname"/authorized_keys
+
+            # make sure authorized_user_ids file exists
+           if [ ! -s "$authorizedUserIDs" ] ; then
+               log "authorized_user_ids file for '$uname' is empty or does not exist."
+               continue
+           fi
+
+           log "processing authorized_keys for user '$uname'..."
+
+           process_authorized_ids "$authorizedUserIDs" "$cacheDir"
+
+            # write output key file
+           log "writing monkeysphere authorized_keys file... "
+           touch "$msAuthorizedKeys"
+           if [ "$(ls "$cacheDir")" ] ; then
+               log -n "adding gpg keys... "
+               cat "$cacheDir"/* > "$msAuthorizedKeys"
+               echo "done."
+           else
+               log "no gpg keys to add."
+           fi
+           if [ "$USER_CONTROLLED_AUTHORIZED_KEYS" ] ; then
+               userHome=$(getent passwd "$uname" | cut -d: -f6)
+               userAuthorizedKeys=${USER_CONTROLLED_AUTHORIZED_KEYS/\%h/"$userHome"}
+               if [ -s "$userAuthorizedKeys" ] ; then
+                   log -n "adding user authorized_keys file... "
+                   cat "$userAuthorizedKeys" >> "$msAuthorizedKeys"
+                   echo "done."
+               fi
+           fi
+           log "monkeysphere authorized_keys file generated:"
+           log "$msAuthorizedKeys"
+       done
+       ;;
+
+    'gen-key'|'g')
+       gen_key
+       ;;
+
+    'publish-key'|'p')
+       publish_key
+       ;;
+
+    'trust-key'|'t')
+       if [ -z "$1" ] ; then
+           failure "you must specify at least one key to trust."
+       fi
+       trust_key "$@"
+       ;;
+
+    'update-user-userid'|'u')
+       uname="$1"
+       shift
+       if [ -z "$uname" ] ; then
+           failure "you must specify user."
+       fi
+       if [ -z "$1" ] ; then
+           failure "you must specify at least one userid."
+       fi
+       for userID ; do
+           AUTHORIZED_USER_IDS="$MS_HOME"/authorized_user_ids/"$uname"
+           if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
+               log "userid '$userID' not in authorized_user_ids file."
+               continue
+           fi
+           log "processing user id: '$userID'"
+           process_user_id "$userID" "$userKeysCacheDir" > /dev/null
+       done
+       ;;
+
+    'help'|'h'|'?')
+        usage
+        ;;
+
+    *)
+        failure "Unknown command: '$COMMAND'
+Type 'cereal-admin help' for usage."
+        ;;
+esac
diff --git a/src/monkeysphere-ssh-proxycommand b/src/monkeysphere-ssh-proxycommand
new file mode 100755 (executable)
index 0000000..1724966
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh -e
+
+# MonkeySphere ssh ProxyCommand hook
+# Proxy command script to initiate a monkeysphere known_hosts update
+# before an ssh connection to host is established.
+# Can be added to ~/.ssh/config as follows:
+# ProxyCommand monkeysphere-ssh-proxycommand %h %p
+
+HOST="$1"
+PORT="$2"
+
+# update the known_hosts file for the host
+monkeysphere update-known-hosts "$HOST"
+
+# make a netcat connection to host for the ssh connection
+exec nc "$HOST" "$PORT"
similarity index 100%
rename from rhesus/README
rename to src/rhesus/README
similarity index 100%
rename from rhesus/rhesus
rename to src/rhesus/rhesus