Merge commit 'jrollins/master'
[monkeysphere.git] / src / monkeysphere-server
1 #!/bin/bash
2
3 # monkeysphere-server: MonkeySphere server admin tool
4 #
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
7 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
8 #
9 # They are Copyright 2008, and are all released under the GPL, version 3
10 # or later.
11
12 ########################################################################
13 PGRM=$(basename $0)
14
15 SHARE=${MONKEYSPHERE_SHARE:="/usr/share/monkeysphere"}
16 export SHARE
17 . "${SHARE}/common" || exit 1
18
19 VARLIB="/var/lib/monkeysphere"
20 export VARLIB
21
22 # date in UTF format if needed
23 DATE=$(date -u '+%FT%T')
24
25 # unset some environment variables that could screw things up
26 unset GREP_OPTIONS
27
28 # default return code
29 RETURN=0
30
31 ########################################################################
32 # FUNCTIONS
33 ########################################################################
34
35 usage() {
36     cat <<EOF
37 usage: $PGRM <subcommand> [options] [args]
38 MonkeySphere server admin tool.
39
40 subcommands:
41  update-users (u) [USER]...          update user authorized_keys files
42
43  gen-key (g) [NAME[:PORT]]           generate gpg key for the server
44    --length (-l) BITS                  key length in bits (2048)
45    --expire (-e) EXPIRE                date to expire
46    --revoker (-r) FINGERPRINT          add a revoker
47  extend-key (e) EXPIRE               extend expiration to EXPIRE
48  add-hostname (n+) NAME[:PORT]       add hostname user ID to server key
49  revoke-hostname (n-) NAME[:PORT]    revoke hostname user ID
50  show-key (s)                        output all server host key information
51  publish-key (p)                     publish server host key to keyserver
52  diagnostics (d)                     report on server monkeysphere status
53
54  add-id-certifier (c+) KEYID         import and tsign a certification key
55    --domain (-n) DOMAIN                limit ID certifications to DOMAIN
56    --trust (-t) TRUST                  trust level of certifier (full)
57    --depth (-d) DEPTH                  trust depth for certifier (1)
58  remove-id-certifier (c-) KEYID      remove a certification key
59  list-id-certifiers (c)              list certification keys
60
61  gpg-authentication-cmd CMD          gnupg-authentication command
62
63  help (h,?)                          this help
64
65 EOF
66 }
67
68 su_monkeysphere_user() {
69     su --preserve-environment "$MONKEYSPHERE_USER" -- -c "$@"
70 }
71
72 # function to interact with the host gnupg keyring
73 gpg_host() {
74     local returnCode
75
76     GNUPGHOME="$GNUPGHOME_HOST"
77     export GNUPGHOME
78
79     # NOTE: we supress this warning because we need the monkeysphere
80     # user to be able to read the host pubring.  we realize this might
81     # be problematic, but it's the simplest solution, without too much
82     # loss of security.
83     gpg --no-permission-warning "$@"
84     returnCode="$?"
85
86     # always reset the permissions on the host pubring so that the
87     # monkeysphere user can read the trust signatures
88     chgrp "$MONKEYSPHERE_USER" "${GNUPGHOME_HOST}/pubring.gpg"
89     chmod g+r "${GNUPGHOME_HOST}/pubring.gpg"
90     
91     return "$returnCode"
92 }
93
94 # function to interact with the authentication gnupg keyring
95 # FIXME: this function requires basically accepts only a single
96 # argument because of problems with quote expansion.  this needs to be
97 # fixed/improved.
98 gpg_authentication() {
99     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
100     export GNUPGHOME
101
102     su_monkeysphere_user "gpg $@"
103 }
104
105 # output just key fingerprint
106 fingerprint_server_key() {
107     gpg_host --list-secret-keys --fingerprint \
108         --with-colons --fixed-list-mode 2> /dev/null | \
109         grep '^fpr:' | head -1 | cut -d: -f10
110 }
111
112 # output key information
113 show_server_key() {
114     local fingerprint
115     local tmpkey
116
117     fingerprint=$(fingerprint_server_key)
118     gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprint"
119
120     # dumping to a file named ' ' so that the ssh-keygen output
121     # doesn't claim any potentially bogus hostname(s):
122     tmpkey=$(mktemp -d)
123     gpg_authentication "--export $fingerprint" | openpgp2ssh "$fingerprint" 2>/dev/null > "$tmpkey/ "
124     echo -n "ssh fingerprint: "
125     (cd "$tmpkey" && ssh-keygen -l -f ' ' | awk '{ print $2 }')
126     rm -rf "$tmpkey"
127     echo -n "OpenPGP fingerprint: "
128     echo "$fingerprint"
129 }
130
131 # update authorized_keys for users
132 update_users() {
133     if [ "$1" ] ; then
134         # get users from command line
135         unames="$@"
136     else
137         # or just look at all users if none specified
138         unames=$(getent passwd | cut -d: -f1)
139     fi
140
141     # set mode
142     MODE="authorized_keys"
143
144     # set gnupg home
145     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
146
147     # check to see if the gpg trust database has been initialized
148     if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then
149         failure "GNUPG trust database uninitialized.  Please see MONKEYSPHERE-SERVER(8)."
150     fi
151
152     # make sure the authorized_keys directory exists
153     mkdir -p "${VARLIB}/authorized_keys"
154
155     # loop over users
156     for uname in $unames ; do
157         # check all specified users exist
158         if ! getent passwd "$uname" >/dev/null ; then
159             log "----- unknown user '$uname' -----"
160             continue
161         fi
162
163         # set authorized_user_ids and raw authorized_keys variables,
164         # translating ssh-style path variables
165         authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
166         rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS")
167
168         # if neither is found, skip user
169         if [ ! -s "$authorizedUserIDs" ] ; then
170             if [ "$rawAuthorizedKeys" = '-' -o ! -s "$rawAuthorizedKeys" ] ; then
171                 continue
172             fi
173         fi
174
175         log "----- user: $uname -----"
176
177         # exit if the authorized_user_ids file is empty
178         if ! check_key_file_permissions "$uname" "$AUTHORIZED_USER_IDS" ; then
179             log "Improper permissions on authorized_user_ids file path."
180             continue
181         fi
182
183         # check permissions on the authorized_keys file path
184         if ! check_key_file_permissions "$uname" "$RAW_AUTHORIZED_KEYS" ; then
185             log "Improper permissions on authorized_keys file path path."
186             continue
187         fi
188
189         # make temporary directory
190         TMPDIR=$(mktemp -d)
191
192         # trap to delete temporary directory on exit
193         trap "rm -rf $TMPDIR" EXIT
194
195         # create temporary authorized_user_ids file
196         TMP_AUTHORIZED_USER_IDS="${TMPDIR}/authorized_user_ids"
197         touch "$TMP_AUTHORIZED_USER_IDS"
198
199         # create temporary authorized_keys file
200         AUTHORIZED_KEYS="${TMPDIR}/authorized_keys"
201         touch "$AUTHORIZED_KEYS"
202
203         # set restrictive permissions on the temporary files
204         # FIXME: is there a better way to do this?
205         chmod 0700 "$TMPDIR"
206         chmod 0600 "$AUTHORIZED_KEYS"
207         chmod 0600 "$TMP_AUTHORIZED_USER_IDS"
208         chown -R "$MONKEYSPHERE_USER" "$TMPDIR"
209
210         # if the authorized_user_ids file exists...
211         if [ -s "$authorizedUserIDs" ] ; then
212             # copy user authorized_user_ids file to temporary
213             # location
214             cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS"
215
216             # export needed variables
217             export AUTHORIZED_KEYS
218             export TMP_AUTHORIZED_USER_IDS
219
220             # process authorized_user_ids file, as monkeysphere
221             # user
222             su_monkeysphere_user \
223                 ". ${SHARE}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS"
224             RETURN="$?"
225         fi
226
227         # add user-controlled authorized_keys file path if specified
228         if [ "$rawAuthorizedKeys" != '-' -a -s "$rawAuthorizedKeys" ] ; then
229             log -n "adding raw authorized_keys file... "
230             cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS"
231             loge "done."
232         fi
233
234         # openssh appears to check the contents of the
235         # authorized_keys file as the user in question, so the
236         # file must be readable by that user at least.
237         # FIXME: is there a better way to do this?
238         chown root "$AUTHORIZED_KEYS"
239         chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS"
240         chmod g+r "$AUTHORIZED_KEYS"
241
242         # move the resulting authorized_keys file into place
243         mv -f "$AUTHORIZED_KEYS" "${VARLIB}/authorized_keys/${uname}"
244
245         # destroy temporary directory
246         rm -rf "$TMPDIR"
247     done
248 }
249
250 # generate server gpg key
251 gen_key() {
252     local keyType
253     local keyLength
254     local keyUsage
255     local keyExpire
256     local revoker
257     local hostName
258     local userID
259     local keyParameters
260     local fingerprint
261
262     # set default key parameter values
263     keyType="RSA"
264     keyLength="2048"
265     keyUsage="auth"
266     keyExpire=
267     revoker=
268
269     # get options
270     TEMP=$(getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@")
271
272     if [ $? != 0 ] ; then
273         exit 1
274     fi
275
276     # Note the quotes around `$TEMP': they are essential!
277     eval set -- "$TEMP"
278
279     while true ; do
280         case "$1" in
281             -l|--length)
282                 keyLength="$2"
283                 shift 2
284                 ;;
285             -e|--expire)
286                 keyExpire="$2"
287                 shift 2
288                 ;;
289             -r|--revoker)
290                 revoker="$2"
291                 shift 2
292                 ;;
293             --)
294                 shift
295                 ;;
296             *)
297                 break
298                 ;;
299         esac
300     done
301
302     hostName=${1:-$(hostname --fqdn)}
303     userID="ssh://${hostName}"
304
305     # check for presense of key with user ID
306     if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then
307         failure "Key for '$userID' already exists"
308     fi
309
310     # prompt about key expiration if not specified
311     if [ -z "$keyExpire" ] ; then
312         keyExpire=$(get_gpg_expiration)
313     fi
314     if ! test_gpg_expire "$keyExpire" ; then
315         failure "invalid key expiration value '$keyExpire'."
316     fi
317
318     # set key parameters
319     keyParameters=$(cat <<EOF
320 Key-Type: $keyType
321 Key-Length: $keyLength
322 Key-Usage: $keyUsage
323 Name-Real: $userID
324 Expire-Date: $keyExpire
325 EOF
326 )
327
328     # add the revoker field if specified
329     # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
330     # FIXME: key is marked "sensitive"?  is this appropriate?
331     if [ "$revoker" ] ; then
332         keyParameters="${keyParameters}"$(cat <<EOF
333 Revoker: 1:$revoker sensitive
334 EOF
335 )
336     fi
337
338     echo "The following key parameters will be used for the host private key:"
339     echo "$keyParameters"
340
341     read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
342     if [ ${OK/y/Y} != 'Y' ] ; then
343         failure "aborting."
344     fi
345
346     # add commit command
347     keyParameters="${keyParameters}"$(cat <<EOF
348
349 %commit
350 %echo done
351 EOF
352 )
353
354     log "generating server key..."
355     echo "$keyParameters" | gpg_host --batch --gen-key
356
357     # output the server fingerprint
358     fingerprint_server_key "=${userID}"
359
360     # find the key fingerprint of the newly generated key
361     fingerprint=$(fingerprint_server_key)
362
363     # export host ownertrust to authentication keyring
364     log "setting ultimate owner trust for server key..."
365     echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
366
367     # translate the private key to ssh format, and export to a file
368     # for sshs usage.
369     # NOTE: assumes that the primary key is the proper key to use
370     (umask 077 && \
371         gpg_host --export-secret-key "$fingerprint" | \
372         openpgp2ssh "$fingerprint" > "${VARLIB}/ssh_host_rsa_key")
373     log "Private SSH host key output to file: ${VARLIB}/ssh_host_rsa_key"
374 }
375
376 # extend the lifetime of a host key:
377 extend_key() {
378     local fpr=$(fingerprint_server_key)
379     local extendTo="$1"
380
381     if [ -z "$fpr" ] ; then
382         failure "You don't appear to have a MonkeySphere host key on this server.  Try 'monkeysphere-server gen-key' first."
383     fi
384
385     if [ -z "$extendTo" ]; then
386         extendTo=$(get_gpg_expiration)
387     fi
388     if ! test_gpg_expire "$extendTo" ; then
389         failure "invalid expiration value '$extendTo'."
390     fi
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://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 diagnostics() {
558 #  * check on the status and validity of the key and public certificates
559     local seckey
560     local keysfound
561     local curdate
562     local warnwindow
563     local warndate
564     local create
565     local expire
566     local uid
567     local fingerprint
568     local badhostkeys
569     local sshd_config
570
571     # FIXME: what's the correct, cross-platform answer?
572     sshd_config=/etc/ssh/sshd_config
573     seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
574     keysfound=$(echo "$seckey" | grep -c ^sec:)
575     curdate=$(date +%s)
576     # warn when anything is 2 months away from expiration
577     warnwindow='2 months'
578     warndate=$(date +%s -d "$warnwindow")
579
580     echo "Checking host GPG key..."
581     if (( "$keysfound" < 1 )); then
582         echo "! No host key found."
583         echo " - Recommendation: run 'monkeysphere-server gen-key'"
584     elif (( "$keysfound" > 1 )); then
585         echo "! More than one host key found?"
586         # FIXME: recommend a way to resolve this
587     else
588         create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
589         expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
590         fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
591         # check for key expiration:
592         if [ "$expire" ]; then
593             if (( "$expire"  < "$curdate" )); then
594                 echo "! Host key is expired."
595                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
596             elif (( "$expire" < "$warndate" )); then
597                 echo "! Host key expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)
598                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
599             fi
600         fi
601
602         # and weirdnesses:
603         if [ "$create" ] && (( "$create" > "$curdate" )); then
604             echo "! Host key was created in the future(?!). Is your clock correct?"
605             echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
606         fi
607
608         # check for UserID expiration:
609         echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
610         while IFS=: read create expire uid ; do
611             # FIXME: should we be doing any checking on the form
612             # of the User ID?  Should we be unmangling it somehow?
613
614             if [ "$create" ] && (( "$create" > "$curdate" )); then
615                 echo "! User ID '$uid' was created in the future(?!).  Is your clock correct?"
616                 echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
617             fi
618             if [ "$expire" ] ; then
619                 if (( "$expire" < "$curdate" )); then
620                     echo "! User ID '$uid' is expired."
621                         # FIXME: recommend a way to resolve this
622                 elif (( "$expire" < "$warndate" )); then
623                     echo "! User ID '$uid' expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)                
624                     # FIXME: recommend a way to resolve this
625                 fi
626             fi
627         done
628             
629 # FIXME: verify that the host key is properly published to the
630 #   keyservers (do this with the non-privileged user)
631
632 # FIXME: check that there are valid, non-expired certifying signatures
633 #   attached to the host key after fetching from the public keyserver
634 #   (do this with the non-privileged user as well)
635
636 # FIXME: propose adding a revoker to the host key if none exist (do we
637 #   have a way to do that after key generation?)
638
639         # Ensure that the ssh_host_rsa_key file is present and non-empty:
640         echo
641         echo "Checking host SSH key..."
642         if [ ! -s "${VARLIB}/ssh_host_rsa_key" ] ; then
643             echo "! The host key as prepared for SSH (${VARLIB}/ssh_host_rsa_key) is missing or empty."
644         else
645             if [ $(stat -c '%a' "${VARLIB}/ssh_host_rsa_key") != 600 ] ; then
646                 echo "! Permissions seem wrong for ${VARLIB}/ssh_host_rsa_key -- should be 0600."
647             fi
648
649             # propose changes needed for sshd_config (if any)
650             if ! grep -q "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$" "$sshd_config"; then
651                 echo "! $sshd_config does not point to the monkeysphere host key (${VARLIB}/ssh_host_rsa_key)."
652                 echo " - Recommendation: add a line to $sshd_config: 'HostKey ${VARLIB}/ssh_host_rsa_key'"
653             fi
654             if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -q -v "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$") ; then
655                 echo "! $sshd_config refers to some non-monkeysphere host keys:"
656                 echo "$badhostkeys"
657                 echo " - Recommendation: remove the above HostKey lines from $sshd_config"
658             fi
659         fi
660     fi
661
662 # FIXME: look at the ownership/privileges of the various keyrings,
663 #    directories housing them, etc (what should those values be?  can
664 #    we make them as minimal as possible?)
665
666 # FIXME: look to see that the ownertrust rules are set properly on the
667 #    authentication keyring
668
669 # FIXME:  make sure that at least one identity certifier exists
670
671     echo
672     echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
673     # Ensure that User ID authentication is enabled:
674     if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$" "$sshd_config"; then
675         echo "! $sshd_config does not point to monkeysphere authorized keys."
676         echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${VARLIB}/authorized_keys/%u'"
677     fi
678     if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -q -v "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$") ; then
679         echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
680         echo "$badauthorizedkeys"
681         echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
682     fi
683 }
684
685 # retrieve key from web of trust, import it into the host keyring, and
686 # ltsign the key in the host keyring so that it may certify other keys
687 add_certifier() {
688     local domain
689     local trust
690     local depth
691     local keyID
692     local fingerprint
693     local ltsignCommand
694     local trustval
695
696     # set default values for trust depth and domain
697     domain=
698     trust=full
699     depth=1
700
701     # get options
702     TEMP=$(getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@")
703
704     if [ $? != 0 ] ; then
705         exit 1
706     fi
707
708     # Note the quotes around `$TEMP': they are essential!
709     eval set -- "$TEMP"
710
711     while true ; do
712         case "$1" in
713             -n|--domain)
714                 domain="$2"
715                 shift 2
716                 ;;
717             -t|--trust)
718                 trust="$2"
719                 shift 2
720                 ;;
721             -d|--depth)
722                 depth="$2"
723                 shift 2
724                 ;;
725             --)
726                 shift
727                 ;;
728             *)
729                 break
730                 ;;
731         esac
732     done
733
734     keyID="$1"
735     if [ -z "$keyID" ] ; then
736         failure "You must specify the key ID of a key to add."
737     fi
738     export keyID
739
740     # get the key from the key server
741     gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'"
742
743     # get the full fingerprint of a key ID
744     fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
745         grep '^fpr:' | grep "$keyID" | cut -d: -f10)
746
747     if [ -z "$fingerprint" ] ; then
748         failure "Key '$keyID' not found."
749     fi
750
751     echo
752     echo "key found:"
753     gpg_authentication "--fingerprint 0x${fingerprint}!"
754
755     echo "Are you sure you want to add the above key as a"
756     read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
757     if [ "${OK/y/Y}" != 'Y' ] ; then
758         failure "Identity certifier not added."
759     fi
760
761     # export the key to the host keyring
762     gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
763
764     if [ "$trust" == marginal ]; then
765         trustval=1
766     elif [ "$trust" == full ]; then
767         trustval=2
768     else
769         failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
770     fi
771
772     # ltsign command
773     # NOTE: *all* user IDs will be ltsigned
774     ltsignCommand=$(cat <<EOF
775 ltsign
776 y
777 $trustval
778 $depth
779 $domain
780 y
781 save
782 EOF
783         )
784
785     # ltsign the key
786     if echo "$ltsignCommand" | \
787         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
788
789         # update the trustdb for the authentication keyring
790         gpg_authentication "--check-trustdb"
791
792         echo
793         echo "Identity certifier added."
794     else
795         failure "Problem adding identify certifier."
796     fi
797 }
798
799 # delete a certifiers key from the host keyring
800 remove_certifier() {
801     local keyID
802     local fingerprint
803
804     keyID="$1"
805     if [ -z "$keyID" ] ; then
806         failure "You must specify the key ID of a key to remove."
807     fi
808
809     if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
810         read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
811         if [ "${OK/y/Y}" != 'Y' ] ; then
812             failure "Identity certifier not removed."
813         fi
814     else
815         failure
816     fi
817
818     # delete the requested key
819     if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
820         # delete key from host keyring as well
821         gpg_host --delete-key --batch --yes "0x${keyID}!"
822
823         # update the trustdb for the authentication keyring
824         gpg_authentication "--check-trustdb"
825
826         echo
827         echo "Identity certifier removed."
828     else
829         failure "Problem removing identity certifier."
830     fi
831 }
832
833 # list the host certifiers
834 list_certifiers() {
835     local keys
836     local key
837
838     # find trusted keys in authentication keychain
839     keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
840         grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
841
842     # output keys
843     for key in $keys ; do
844         gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
845     done
846 }
847
848 # issue command to gpg-authentication keyring
849 gpg_authentication_cmd() {
850     gpg_authentication "$@"
851 }
852
853 ########################################################################
854 # MAIN
855 ########################################################################
856
857 # unset variables that should be defined only in config file
858 unset KEYSERVER
859 unset AUTHORIZED_USER_IDS
860 unset RAW_AUTHORIZED_KEYS
861 unset MONKEYSPHERE_USER
862
863 # load configuration file
864 [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${ETC}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
865
866 # set empty config variable with ones from the environment, or with
867 # defaults
868 KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="subkeys.pgp.net"}}
869 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.config/monkeysphere/authorized_user_ids"}}
870 RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
871 MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
872
873 # other variables
874 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
875 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
876 GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${VARLIB}/gnupg-host"}
877 GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${VARLIB}/gnupg-authentication"}
878
879 # export variables needed in su invocation
880 export DATE
881 export MODE
882 export MONKEYSPHERE_USER
883 export KEYSERVER
884 export CHECK_KEYSERVER
885 export REQUIRED_USER_KEY_CAPABILITY
886 export GNUPGHOME_HOST
887 export GNUPGHOME_AUTHENTICATION
888 export GNUPGHOME
889
890 # get subcommand
891 COMMAND="$1"
892 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
893 shift
894
895 case $COMMAND in
896     'update-users'|'update-user'|'u')
897         update_users "$@"
898         ;;
899
900     'gen-key'|'g')
901         gen_key "$@"
902         ;;
903
904     'extend-key'|'e')
905         extend_key "$@"
906         ;;
907
908     'add-hostname'|'add-name'|'n+')
909         add_hostname "$@"
910         ;;
911
912     'revoke-hostname'|'revoke-name'|'n-')
913         revoke_hostname "$@"
914         ;;
915
916     'show-key'|'show'|'s')
917         show_server_key
918         ;;
919
920     'publish-key'|'publish'|'p')
921         publish_server_key
922         ;;
923
924     'diagnostics'|'d')
925         diagnostics
926         ;;
927
928     'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
929         add_certifier "$@"
930         ;;
931
932     'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
933         remove_certifier "$@"
934         ;;
935
936     'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
937         list_certifiers "$@"
938         ;;
939
940     'gpg-authentication-cmd')
941         gpg_authentication_cmd "$@"
942         ;;
943
944     '--help'|'help'|'-h'|'h'|'?')
945         usage
946         ;;
947
948     *)
949         failure "Unknown command: '$COMMAND'
950 Type '$PGRM help' for usage."
951         ;;
952 esac
953
954 exit "$RETURN"