add some documentation to the user section about establishing trust
[monkeysphere.git] / src / monkeysphere-server
1 #!/usr/bin/env bash
2
3 # monkeysphere-server: MonkeySphere server admin tool
4 #
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
7 # Jamie McClelland <jm@mayfirst.org>
8 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
9 #
10 # They are Copyright 2008, and are all released under the GPL, version 3
11 # or later.
12
13 ########################################################################
14 PGRM=$(basename $0)
15
16 SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"}
17 export SYSSHAREDIR
18 . "${SYSSHAREDIR}/common" || exit 1
19
20 SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"}
21 export SYSDATADIR
22
23 # UTC date in ISO 8601 format if needed
24 DATE=$(date -u '+%FT%T')
25
26 # unset some environment variables that could screw things up
27 unset GREP_OPTIONS
28
29 # default return code
30 RETURN=0
31
32 ########################################################################
33 # FUNCTIONS
34 ########################################################################
35
36 usage() {
37     cat <<EOF >&2
38 usage: $PGRM <subcommand> [options] [args]
39 Monkeysphere server admin tool.
40
41 subcommands:
42  update-users (u) [USER]...          update user authorized_keys files
43
44  gen-key (g) [NAME[:PORT]]           generate gpg key for the server
45    --length (-l) BITS                  key length in bits (2048)
46    --expire (-e) EXPIRE                date to expire
47    --revoker (-r) FINGERPRINT          add a revoker
48  extend-key (e) EXPIRE               extend expiration to EXPIRE
49  add-hostname (n+) NAME[:PORT]       add hostname user ID to server key
50  revoke-hostname (n-) NAME[:PORT]    revoke hostname user ID
51  show-key (s)                        output all server host key information
52  publish-key (p)                     publish server host key to keyserver
53  diagnostics (d)                     report on server monkeysphere status
54
55  add-id-certifier (c+) KEYID         import and tsign a certification key
56    --domain (-n) DOMAIN                limit ID certifications to DOMAIN
57    --trust (-t) TRUST                  trust level of certifier (full)
58    --depth (-d) DEPTH                  trust depth for certifier (1)
59  remove-id-certifier (c-) KEYID      remove a certification key
60  list-id-certifiers (c)              list certification keys
61
62  gpg-authentication-cmd CMD          gnupg-authentication command
63
64  help (h,?)                          this help
65
66 EOF
67 }
68
69 su_monkeysphere_user() {
70     su "$MONKEYSPHERE_USER" -c "$@"
71 }
72
73 # function to interact with the host gnupg keyring
74 gpg_host() {
75     local returnCode
76
77     GNUPGHOME="$GNUPGHOME_HOST"
78     export GNUPGHOME
79
80     # NOTE: we supress this warning because we need the monkeysphere
81     # user to be able to read the host pubring.  we realize this might
82     # be problematic, but it's the simplest solution, without too much
83     # loss of security.
84     gpg --no-permission-warning "$@"
85     returnCode="$?"
86
87     # always reset the permissions on the host pubring so that the
88     # monkeysphere user can read the trust signatures
89     chgrp "$MONKEYSPHERE_USER" "${GNUPGHOME_HOST}/pubring.gpg"
90     chmod g+r "${GNUPGHOME_HOST}/pubring.gpg"
91     
92     return "$returnCode"
93 }
94
95 # function to interact with the authentication gnupg keyring
96 # FIXME: this function requires basically accepts only a single
97 # argument because of problems with quote expansion.  this needs to be
98 # fixed/improved.
99 gpg_authentication() {
100     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
101     export GNUPGHOME
102
103     su_monkeysphere_user "gpg $@"
104 }
105
106 # output just key fingerprint
107 fingerprint_server_key() {
108     gpg_host --list-secret-keys --fingerprint \
109         --with-colons --fixed-list-mode 2> /dev/null | \
110         grep '^fpr:' | head -1 | cut -d: -f10
111 }
112
113 # output key information
114 show_server_key() {
115     local fingerprint
116     local tmpkey
117
118     fingerprint=$(fingerprint_server_key)
119     gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprint"
120
121     # dumping to a file named ' ' so that the ssh-keygen output
122     # doesn't claim any potentially bogus hostname(s):
123     tmpkey=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
124     gpg_authentication "--export $fingerprint" | openpgp2ssh "$fingerprint" 2>/dev/null > "$tmpkey/ "
125     echo -n "ssh fingerprint: "
126     (cd "$tmpkey" && ssh-keygen -l -f ' ' | awk '{ print $2 }')
127     rm -rf "$tmpkey"
128     echo -n "OpenPGP fingerprint: "
129     echo "$fingerprint"
130 }
131
132 # update authorized_keys for users
133 update_users() {
134     if [ "$1" ] ; then
135         # get users from command line
136         unames="$@"
137     else
138         # or just look at all users if none specified
139         unames=$(getent passwd | cut -d: -f1)
140     fi
141
142     # set mode
143     MODE="authorized_keys"
144
145     # set gnupg home
146     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
147
148     # check to see if the gpg trust database has been initialized
149     if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then
150         failure "GNUPG trust database uninitialized.  Please see MONKEYSPHERE-SERVER(8)."
151     fi
152
153     # make sure the authorized_keys directory exists
154     mkdir -p "${SYSDATADIR}/authorized_keys"
155
156     # loop over users
157     for uname in $unames ; do
158         # check all specified users exist
159         if ! getent passwd "$uname" >/dev/null ; then
160             log error "----- unknown user '$uname' -----"
161             continue
162         fi
163
164         # set authorized_user_ids and raw authorized_keys variables,
165         # translating ssh-style path variables
166         authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
167         rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS")
168
169         # if neither is found, skip user
170         if [ ! -s "$authorizedUserIDs" ] ; then
171             if [ "$rawAuthorizedKeys" = '-' -o ! -s "$rawAuthorizedKeys" ] ; then
172                 continue
173             fi
174         fi
175
176         log verbose "----- user: $uname -----"
177
178         # exit if the authorized_user_ids file is empty
179         if ! check_key_file_permissions "$uname" "$AUTHORIZED_USER_IDS" ; then
180             log error "Improper permissions on path '$AUTHORIZED_USER_IDS'."
181             continue
182         fi
183
184         # check permissions on the authorized_keys file path
185         if ! check_key_file_permissions "$uname" "$RAW_AUTHORIZED_KEYS" ; then
186             log error "Improper permissions on path '$RAW_AUTHORIZED_KEYS'."
187             continue
188         fi
189
190         # make temporary directory
191         TMPLOC=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
192
193         # trap to delete temporary directory on exit
194         trap "rm -rf $TMPLOC" EXIT
195
196         # create temporary authorized_user_ids file
197         TMP_AUTHORIZED_USER_IDS="${TMPLOC}/authorized_user_ids"
198         touch "$TMP_AUTHORIZED_USER_IDS"
199
200         # create temporary authorized_keys file
201         AUTHORIZED_KEYS="${TMPLOC}/authorized_keys"
202         touch "$AUTHORIZED_KEYS"
203
204         # set restrictive permissions on the temporary files
205         # FIXME: is there a better way to do this?
206         chmod 0700 "$TMPLOC"
207         chmod 0600 "$AUTHORIZED_KEYS"
208         chmod 0600 "$TMP_AUTHORIZED_USER_IDS"
209         chown -R "$MONKEYSPHERE_USER" "$TMPLOC"
210
211         # if the authorized_user_ids file exists...
212         if [ -s "$authorizedUserIDs" ] ; then
213             # copy user authorized_user_ids file to temporary
214             # location
215             cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS"
216
217             # export needed variables
218             export AUTHORIZED_KEYS
219             export TMP_AUTHORIZED_USER_IDS
220
221             # process authorized_user_ids file, as monkeysphere
222             # user
223             su_monkeysphere_user \
224                 ". ${SYSSHAREDIR}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS"
225             RETURN="$?"
226         fi
227
228         # add user-controlled authorized_keys file path if specified
229         if [ "$rawAuthorizedKeys" != '-' -a -s "$rawAuthorizedKeys" ] ; then
230             log verbose "adding raw authorized_keys file... "
231             cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS"
232         fi
233
234         # openssh appears to check the contents of the
235         # authorized_keys file as the user in question, so the
236         # file must be readable by that user at least.
237         # FIXME: is there a better way to do this?
238         chown root "$AUTHORIZED_KEYS"
239         chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS"
240         chmod g+r "$AUTHORIZED_KEYS"
241
242         # move the resulting authorized_keys file into place
243         mv -f "$AUTHORIZED_KEYS" "${SYSDATADIR}/authorized_keys/${uname}"
244
245         # destroy temporary directory
246         rm -rf "$TMPLOC"
247     done
248 }
249
250 # generate server gpg key
251 gen_key() {
252     local keyType
253     local keyLength
254     local keyUsage
255     local keyExpire
256     local revoker
257     local hostName
258     local userID
259     local keyParameters
260     local fingerprint
261
262     # set default key parameter values
263     keyType="RSA"
264     keyLength="2048"
265     keyUsage="auth"
266     keyExpire=
267     revoker=
268
269     # get options
270     TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@") || failure "getopt failed!  Does your getopt support GNU-style long options?"
271
272     if [ $? != 0 ] ; then
273         exit 1
274     fi
275
276     # Note the quotes around `$TEMP': they are essential!
277     eval set -- "$TEMP"
278
279     while true ; do
280         case "$1" in
281             -l|--length)
282                 keyLength="$2"
283                 shift 2
284                 ;;
285             -e|--expire)
286                 keyExpire="$2"
287                 shift 2
288                 ;;
289             -r|--revoker)
290                 revoker="$2"
291                 shift 2
292                 ;;
293             --)
294                 shift
295                 ;;
296             *)
297                 break
298                 ;;
299         esac
300     done
301
302     hostName=${1:-$(hostname -f)}
303     userID="ssh://${hostName}"
304
305     # check for presense of key with user ID
306     if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then
307         failure "Key for '$userID' already exists"
308     fi
309
310     # prompt about key expiration if not specified
311     keyExpire=$(get_gpg_expiration "$keyExpire")
312
313     # set key parameters
314     keyParameters=$(cat <<EOF
315 Key-Type: $keyType
316 Key-Length: $keyLength
317 Key-Usage: $keyUsage
318 Name-Real: $userID
319 Expire-Date: $keyExpire
320 EOF
321 )
322
323     # add the revoker field if specified
324     # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
325     # FIXME: key is marked "sensitive"?  is this appropriate?
326     if [ "$revoker" ] ; then
327         keyParameters="${keyParameters}"$(cat <<EOF
328 Revoker: 1:$revoker sensitive
329 EOF
330 )
331     fi
332
333     echo "The following key parameters will be used for the host private key:"
334     echo "$keyParameters"
335
336     read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
337     if [ ${OK/y/Y} != 'Y' ] ; then
338         failure "aborting."
339     fi
340
341     # add commit command
342     keyParameters="${keyParameters}"$(cat <<EOF
343
344 %commit
345 %echo done
346 EOF
347 )
348
349     log verbose "generating server key..."
350     echo "$keyParameters" | gpg_host --batch --gen-key
351
352     # output the server fingerprint
353     fingerprint_server_key "=${userID}"
354
355     # find the key fingerprint of the newly generated key
356     fingerprint=$(fingerprint_server_key)
357
358     # export host ownertrust to authentication keyring
359     log verbose "setting ultimate owner trust for server key..."
360     echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
361
362     # translate the private key to ssh format, and export to a file
363     # for sshs usage.
364     # NOTE: assumes that the primary key is the proper key to use
365     (umask 077 && \
366         gpg_host --export-secret-key "$fingerprint" | \
367         openpgp2ssh "$fingerprint" > "${SYSDATADIR}/ssh_host_rsa_key")
368     log info "Private SSH host key output to file: ${SYSDATADIR}/ssh_host_rsa_key"
369 }
370
371 # extend the lifetime of a host key:
372 extend_key() {
373     local fpr=$(fingerprint_server_key)
374     local extendTo="$1"
375
376     if [ -z "$fpr" ] ; then
377         failure "You don't appear to have a MonkeySphere host key on this server.  Try 'monkeysphere-server gen-key' first."
378     fi
379
380     # get the new expiration date
381     extendTo=$(get_gpg_expiration "$extendTo")
382
383     gpg_host --quiet --command-fd 0 --edit-key "$fpr" <<EOF 
384 expire
385 $extendTo
386 save
387 EOF
388
389     echo
390     echo "NOTE: Host key expiration date adjusted, but not yet published."
391     echo "Run '$PGRM publish-key' to publish the new expiration date."
392 }
393
394 # add hostname user ID to server key
395 add_hostname() {
396     local userID
397     local fingerprint
398     local tmpuidMatch
399     local line
400     local adduidCommand
401
402     if [ -z "$1" ] ; then
403         failure "You must specify a hostname to add."
404     fi
405
406     userID="ssh://${1}"
407
408     fingerprint=$(fingerprint_server_key)
409
410     # match to only ultimately trusted user IDs
411     tmpuidMatch="u:$(echo $userID | gpg_escape)"
412
413     # find the index of the requsted user ID
414     # NOTE: this is based on circumstantial evidence that the order of
415     # this output is the appropriate index
416     if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
417         | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
418         failure "Host userID '$userID' already exists."
419     fi
420
421     echo "The following user ID will be added to the host key:"
422     echo "  $userID"
423     read -p "Are you sure you would like to add this user ID? (y/N) " OK; OK=${OK:=N}
424     if [ ${OK/y/Y} != 'Y' ] ; then
425         failure "User ID not added."
426     fi
427
428     # edit-key script command to add user ID
429     adduidCommand=$(cat <<EOF
430 adduid
431 $userID
432
433
434 save
435 EOF
436         )
437
438     # execute edit-key script
439     if echo "$adduidCommand" | \
440         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
441
442         # update the trustdb for the authentication keyring
443         gpg_authentication "--check-trustdb"
444
445         show_server_key
446
447         echo
448         echo "NOTE: User ID added to key, but key not published."
449         echo "Run '$PGRM publish-key' to publish the new user ID."
450     else
451         failure "Problem adding user ID."
452     fi
453 }
454
455 # revoke hostname user ID to server key
456 revoke_hostname() {
457     local userID
458     local fingerprint
459     local tmpuidMatch
460     local line
461     local uidIndex
462     local message
463     local revuidCommand
464
465     if [ -z "$1" ] ; then
466         failure "You must specify a hostname to revoke."
467     fi
468
469     echo "WARNING: There is a known bug in this function."
470     echo "This function has been known to occasionally revoke the wrong user ID."
471     echo "Please see the following bug report for more information:"
472     echo "http://web.monkeysphere.info/bugs/revoke-hostname-revoking-wrong-userid/"
473     read -p "Are you sure you would like to proceed? (y/N) " OK; OK=${OK:=N}
474     if [ ${OK/y/Y} != 'Y' ] ; then
475         failure "aborting."
476     fi
477
478     userID="ssh://${1}"
479
480     fingerprint=$(fingerprint_server_key)
481
482     # match to only ultimately trusted user IDs
483     tmpuidMatch="u:$(echo $userID | gpg_escape)"
484
485     # find the index of the requsted user ID
486     # NOTE: this is based on circumstantial evidence that the order of
487     # this output is the appropriate index
488     if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
489         | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
490         uidIndex=${line%%:*}
491     else
492         failure "No non-revoked user ID '$userID' is found."
493     fi
494
495     echo "The following host key user ID will be revoked:"
496     echo "  $userID"
497     read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
498     if [ ${OK/y/Y} != 'Y' ] ; then
499         failure "User ID not revoked."
500     fi
501
502     message="Hostname removed by monkeysphere-server $DATE"
503
504     # edit-key script command to revoke user ID
505     revuidCommand=$(cat <<EOF
506 $uidIndex
507 revuid
508 y
509 4
510 $message
511
512 y
513 save
514 EOF
515         )       
516
517     # execute edit-key script
518     if echo "$revuidCommand" | \
519         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
520
521         # update the trustdb for the authentication keyring
522         gpg_authentication "--check-trustdb"
523
524         show_server_key
525
526         echo
527         echo "NOTE: User ID revoked, but revocation not published."
528         echo "Run '$PGRM publish-key' to publish the revocation."
529     else
530         failure "Problem revoking user ID."
531     fi
532 }
533
534 # publish server key to keyserver
535 publish_server_key() {
536     read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
537     if [ ${OK/y/Y} != 'Y' ] ; then
538         failure "key not published."
539     fi
540
541     # find the key fingerprint
542     fingerprint=$(fingerprint_server_key)
543
544     # publish host key
545     gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
546 }
547
548
549 diagnostics() {
550 #  * check on the status and validity of the key and public certificates
551     local seckey
552     local keysfound
553     local curdate
554     local warnwindow
555     local warndate
556     local create
557     local expire
558     local uid
559     local fingerprint
560     local badhostkeys
561     local sshd_config
562     local problemsfound=0
563
564     # FIXME: what's the correct, cross-platform answer?
565     sshd_config=/etc/ssh/sshd_config
566     seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
567     keysfound=$(echo "$seckey" | grep -c ^sec:)
568     curdate=$(date +%s)
569     # warn when anything is 2 months away from expiration
570     warnwindow='2 months'
571     warndate=$(advance_date $warnwindow +%s)
572
573     if ! id monkeysphere >/dev/null ; then
574         echo "! No monkeysphere user found!  Please create a monkeysphere system user with bash as its shell."
575         problemsfound=$(($problemsfound+1))
576     fi
577
578     if ! [ -d "$SYSDATADIR" ] ; then
579         echo "! no $SYSDATADIR directory found.  Please create it."
580         problemsfound=$(($problemsfound+1))
581     fi
582
583     echo "Checking host GPG key..."
584     if (( "$keysfound" < 1 )); then
585         echo "! No host key found."
586         echo " - Recommendation: run 'monkeysphere-server gen-key'"
587         problemsfound=$(($problemsfound+1))
588     elif (( "$keysfound" > 1 )); then
589         echo "! More than one host key found?"
590         # FIXME: recommend a way to resolve this
591         problemsfound=$(($problemsfound+1))
592     else
593         create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
594         expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
595         fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
596         # check for key expiration:
597         if [ "$expire" ]; then
598             if (( "$expire"  < "$curdate" )); then
599                 echo "! Host key is expired."
600                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
601                 problemsfound=$(($problemsfound+1))
602             elif (( "$expire" < "$warndate" )); then
603                 echo "! Host key expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
604                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
605                 problemsfound=$(($problemsfound+1))
606             fi
607         fi
608
609         # and weirdnesses:
610         if [ "$create" ] && (( "$create" > "$curdate" )); then
611             echo "! Host key was created in the future(?!). Is your clock correct?"
612             echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
613             problemsfound=$(($problemsfound+1))
614         fi
615
616         # check for UserID expiration:
617         echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
618         while IFS=: read create expire uid ; do
619             # FIXME: should we be doing any checking on the form
620             # of the User ID?  Should we be unmangling it somehow?
621
622             if [ "$create" ] && (( "$create" > "$curdate" )); then
623                 echo "! User ID '$uid' was created in the future(?!).  Is your clock correct?"
624                 echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
625                 problemsfound=$(($problemsfound+1))
626             fi
627             if [ "$expire" ] ; then
628                 if (( "$expire" < "$curdate" )); then
629                     echo "! User ID '$uid' is expired."
630                     # FIXME: recommend a way to resolve this
631                     problemsfound=$(($problemsfound+1))
632                 elif (( "$expire" < "$warndate" )); then
633                     echo "! User ID '$uid' expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)             
634                     # FIXME: recommend a way to resolve this
635                     problemsfound=$(($problemsfound+1))
636                 fi
637             fi
638         done
639             
640 # FIXME: verify that the host key is properly published to the
641 #   keyservers (do this with the non-privileged user)
642
643 # FIXME: check that there are valid, non-expired certifying signatures
644 #   attached to the host key after fetching from the public keyserver
645 #   (do this with the non-privileged user as well)
646
647 # FIXME: propose adding a revoker to the host key if none exist (do we
648 #   have a way to do that after key generation?)
649
650         # Ensure that the ssh_host_rsa_key file is present and non-empty:
651         echo
652         echo "Checking host SSH key..."
653         if [ ! -s "${SYSDATADIR}/ssh_host_rsa_key" ] ; then
654             echo "! The host key as prepared for SSH (${SYSDATADIR}/ssh_host_rsa_key) is missing or empty."
655             problemsfound=$(($problemsfound+1))
656         else
657             if [ $(ls -l "${SYSDATADIR}/ssh_host_rsa_key" | cut -f1 -d\ ) != '-rw-------' ] ; then
658                 echo "! Permissions seem wrong for ${SYSDATADIR}/ssh_host_rsa_key -- should be 0600."
659                 problemsfound=$(($problemsfound+1))
660             fi
661
662             # propose changes needed for sshd_config (if any)
663             if ! grep -q "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$" "$sshd_config"; then
664                 echo "! $sshd_config does not point to the monkeysphere host key (${SYSDATADIR}/ssh_host_rsa_key)."
665                 echo " - Recommendation: add a line to $sshd_config: 'HostKey ${SYSDATADIR}/ssh_host_rsa_key'"
666                 problemsfound=$(($problemsfound+1))
667             fi
668             if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -v "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$") ; then
669                 echo "! $sshd_config refers to some non-monkeysphere host keys:"
670                 echo "$badhostkeys"
671                 echo " - Recommendation: remove the above HostKey lines from $sshd_config"
672                 problemsfound=$(($problemsfound+1))
673             fi
674         fi
675     fi
676
677 # FIXME: look at the ownership/privileges of the various keyrings,
678 #    directories housing them, etc (what should those values be?  can
679 #    we make them as minimal as possible?)
680
681 # FIXME: look to see that the ownertrust rules are set properly on the
682 #    authentication keyring
683
684 # FIXME: make sure that at least one identity certifier exists
685
686 # FIXME: look at the timestamps on the monkeysphere-generated
687 # authorized_keys files -- warn if they seem out-of-date.
688
689 # FIXME: check for a cronjob that updates monkeysphere-generated
690 # authorized_keys?
691
692     echo
693     echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
694     # Ensure that User ID authentication is enabled:
695     if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$" "$sshd_config"; then
696         echo "! $sshd_config does not point to monkeysphere authorized keys."
697         echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${SYSDATADIR}/authorized_keys/%u'"
698         problemsfound=$(($problemsfound+1))
699     fi
700     if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -v "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$") ; then
701         echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
702         echo "$badauthorizedkeys"
703         echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
704         problemsfound=$(($problemsfound+1))
705     fi
706
707     if [ "$problemsfound" -gt 0 ]; then
708         echo "When the above $problemsfound issue"$(if [ "$problemsfound" -eq 1 ] ; then echo " is" ; else echo "s are" ; fi)" resolved, please re-run:"
709         echo "  monkeysphere-server diagnostics"
710     else
711         echo "Everything seems to be in order!"
712     fi
713 }
714
715 # retrieve key from web of trust, import it into the host keyring, and
716 # ltsign the key in the host keyring so that it may certify other keys
717 add_certifier() {
718     local domain
719     local trust
720     local depth
721     local keyID
722     local fingerprint
723     local ltsignCommand
724     local trustval
725
726     # set default values for trust depth and domain
727     domain=
728     trust=full
729     depth=1
730
731     # get options
732     TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@") || failure "getopt failed!  Does your getopt support GNU-style long options?"
733
734     if [ $? != 0 ] ; then
735         exit 1
736     fi
737
738     # Note the quotes around `$TEMP': they are essential!
739     eval set -- "$TEMP"
740
741     while true ; do
742         case "$1" in
743             -n|--domain)
744                 domain="$2"
745                 shift 2
746                 ;;
747             -t|--trust)
748                 trust="$2"
749                 shift 2
750                 ;;
751             -d|--depth)
752                 depth="$2"
753                 shift 2
754                 ;;
755             --)
756                 shift
757                 ;;
758             *)
759                 break
760                 ;;
761         esac
762     done
763
764     keyID="$1"
765     if [ -z "$keyID" ] ; then
766         failure "You must specify the key ID of a key to add, or specify a file to read the key from."
767     fi
768     if [ -f "$keyID" ] ; then
769         echo "Reading key from file '$keyID':"
770         importinfo=$(gpg_authentication "--import" < "$keyID" 2>&1) || failure "could not read key from '$keyID'"
771         # FIXME: if this is tried when the key database is not
772         # up-to-date, i got these errors (using set -x):
773
774 # ++ su -m monkeysphere -c '\''gpg --import'\''
775 # Warning: using insecure memory!
776 # gpg: key D21739E9: public key "Daniel Kahn Gillmor <dkg@fifthhorseman.net>" imported
777 # gpg: Total number processed: 1
778 # gpg:               imported: 1  (RSA: 1)
779 # gpg: can'\''t create `/var/monkeysphere/gnupg-host/pubring.gpg.tmp'\'': Permission denied
780 # gpg: failed to rebuild keyring cache: Permission denied
781 # gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
782 # gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
783 # gpg: next trustdb check due at 2009-01-10'
784 # + failure 'could not read key from '\''/root/dkg.gpg'\'''
785 # + echo 'could not read key from '\''/root/dkg.gpg'\'''
786
787         keyID=$(echo "$importinfo" | grep '^gpg: key ' | cut -f2 -d: | cut -f3 -d\ )
788         if [ -z "$keyID" ] || [ $(echo "$keyID" | wc -l) -ne 1 ] ; then
789             failure "Expected there to be a single gpg key in the file."
790         fi
791     else
792         # get the key from the key server
793         gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'" || failure "Could not receive a key with this ID from the '$KEYSERVER' keyserver."
794     fi
795
796     export keyID
797
798
799     # get the full fingerprint of a key ID
800     fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
801         grep '^fpr:' | grep "$keyID" | cut -d: -f10)
802
803     if [ -z "$fingerprint" ] ; then
804         failure "Key '$keyID' not found."
805     fi
806
807     echo
808     echo "key found:"
809     gpg_authentication "--fingerprint 0x${fingerprint}!"
810
811     echo "Are you sure you want to add the above key as a"
812     read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
813     if [ "${OK/y/Y}" != 'Y' ] ; then
814         failure "Identity certifier not added."
815     fi
816
817     # export the key to the host keyring
818     gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
819
820     if [ "$trust" == marginal ]; then
821         trustval=1
822     elif [ "$trust" == full ]; then
823         trustval=2
824     else
825         failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
826     fi
827
828     # ltsign command
829     # NOTE: *all* user IDs will be ltsigned
830     ltsignCommand=$(cat <<EOF
831 ltsign
832 y
833 $trustval
834 $depth
835 $domain
836 y
837 save
838 EOF
839         )
840
841     # ltsign the key
842     if echo "$ltsignCommand" | \
843         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
844
845         # update the trustdb for the authentication keyring
846         gpg_authentication "--check-trustdb"
847
848         echo
849         echo "Identity certifier added."
850     else
851         failure "Problem adding identify certifier."
852     fi
853 }
854
855 # delete a certifiers key from the host keyring
856 remove_certifier() {
857     local keyID
858     local fingerprint
859
860     keyID="$1"
861     if [ -z "$keyID" ] ; then
862         failure "You must specify the key ID of a key to remove."
863     fi
864
865     if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
866         read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
867         if [ "${OK/y/Y}" != 'Y' ] ; then
868             failure "Identity certifier not removed."
869         fi
870     else
871         failure
872     fi
873
874     # delete the requested key
875     if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
876         # delete key from host keyring as well
877         gpg_host --delete-key --batch --yes "0x${keyID}!"
878
879         # update the trustdb for the authentication keyring
880         gpg_authentication "--check-trustdb"
881
882         echo
883         echo "Identity certifier removed."
884     else
885         failure "Problem removing identity certifier."
886     fi
887 }
888
889 # list the host certifiers
890 list_certifiers() {
891     local keys
892     local key
893
894     # find trusted keys in authentication keychain
895     keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
896         grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
897
898     # output keys
899     for key in $keys ; do
900         gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
901     done
902 }
903
904 # issue command to gpg-authentication keyring
905 gpg_authentication_cmd() {
906     gpg_authentication "$@"
907 }
908
909 ########################################################################
910 # MAIN
911 ########################################################################
912
913 # unset variables that should be defined only in config file
914 unset KEYSERVER
915 unset AUTHORIZED_USER_IDS
916 unset RAW_AUTHORIZED_KEYS
917 unset MONKEYSPHERE_USER
918
919 # load configuration file
920 [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${SYSCONFIGDIR}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
921
922 # set empty config variable with ones from the environment, or with
923 # defaults
924 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
925 KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="pool.sks-keyservers.net"}}
926 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.monkeysphere/authorized_user_ids"}}
927 RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
928 MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
929
930 # other variables
931 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
932 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
933 GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${SYSDATADIR}/gnupg-host"}
934 GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${SYSDATADIR}/gnupg-authentication"}
935
936 # export variables needed in su invocation
937 export DATE
938 export MODE
939 export MONKEYSPHERE_USER
940 export LOG_LEVEL
941 export KEYSERVER
942 export CHECK_KEYSERVER
943 export REQUIRED_USER_KEY_CAPABILITY
944 export GNUPGHOME_HOST
945 export GNUPGHOME_AUTHENTICATION
946 export GNUPGHOME
947
948 # get subcommand
949 COMMAND="$1"
950 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
951 shift
952
953 case $COMMAND in
954     'update-users'|'update-user'|'u')
955         update_users "$@"
956         ;;
957
958     'gen-key'|'g')
959         gen_key "$@"
960         ;;
961
962     'extend-key'|'e')
963         extend_key "$@"
964         ;;
965
966     'add-hostname'|'add-name'|'n+')
967         add_hostname "$@"
968         ;;
969
970     'revoke-hostname'|'revoke-name'|'n-')
971         revoke_hostname "$@"
972         ;;
973
974     'show-key'|'show'|'s')
975         show_server_key
976         ;;
977
978     'publish-key'|'publish'|'p')
979         publish_server_key
980         ;;
981
982     'diagnostics'|'d')
983         diagnostics
984         ;;
985
986     'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
987         add_certifier "$@"
988         ;;
989
990     'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
991         remove_certifier "$@"
992         ;;
993
994     'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
995         list_certifiers "$@"
996         ;;
997
998     'gpg-authentication-cmd')
999         gpg_authentication_cmd "$@"
1000         ;;
1001
1002     '--help'|'help'|'-h'|'h'|'?')
1003         usage
1004         ;;
1005
1006     *)
1007         failure "Unknown command: '$COMMAND'
1008 Type '$PGRM help' for usage."
1009         ;;
1010 esac
1011
1012 exit "$RETURN"