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