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