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