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