fcd311485342e1c38d1db9305e57950bef036608
[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     userID="ssh://${1}"
450
451     fingerprint=$(fingerprint_server_key)
452
453     # match to only ultimately trusted user IDs
454     tmpuidMatch="u:$(echo $userID | gpg_escape)"
455
456     # find the index of the requsted user ID
457     # NOTE: this is based on circumstantial evidence that the order of
458     # this output is the appropriate index
459     if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
460         | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
461         uidIndex=${line%%:*}
462     else
463         failure "No non-revoked user ID '$userID' is found."
464     fi
465
466     echo "The following host key user ID will be revoked:"
467     echo "  $userID"
468     read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
469     if [ ${OK/y/Y} != 'Y' ] ; then
470         failure "User ID not revoked."
471     fi
472
473     message="Hostname removed by monkeysphere-server $DATE"
474
475     # edit-key script command to revoke user ID
476     revuidCommand=$(cat <<EOF
477 $uidIndex
478 revuid
479 y
480 4
481 $message
482
483 y
484 save
485 EOF
486         )       
487
488     # execute edit-key script
489     if echo "$revuidCommand" | \
490         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
491         # update trust db
492         gpg_host --check-trustdb
493
494         show_server_key
495
496         echo "NOTE: User ID revoked, but revokation not published."
497         echo "Run '$PGRM publish-key' to publish the revocation."
498     else
499         failure "Problem revoking user ID."
500     fi
501 }
502
503 # publish server key to keyserver
504 publish_server_key() {
505     read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
506     if [ ${OK/y/Y} != 'Y' ] ; then
507         failure "key not published."
508     fi
509
510     # find the key fingerprint
511     fingerprint=$(fingerprint_server_key)
512
513     # publish host key
514     gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
515 }
516
517 diagnostics() {
518 #  * check on the status and validity of the key and public certificates
519     local seckey
520     local keysfound
521     local curdate
522     local warnwindow
523     local warndate
524     local create
525     local expire
526     local uid
527     local fingerprint
528     local badhostkeys
529     local sshd_config
530
531     # FIXME: what's the correct, cross-platform answer?
532     sshd_config=/etc/ssh/sshd_config
533     seckey=$(fingerprint_server_key)
534     keysfound=$(echo "$seckey" | grep -c ^sec:)
535     curdate=$(date +%s)
536     # warn when anything is 2 months away from expiration
537     warnwindow='2 months'
538     warndate=$(date +%s -d "$warnwindow")
539
540     echo "Checking host GPG key..."
541     if (( "$keysfound" < 1 )); then
542         echo "! No host key found."
543         echo " - Recommendation: run 'monkeysphere-server gen-key'"
544     elif (( "$keysfound" > 1 )); then
545         echo "! More than one host key found?"
546         # FIXME: recommend a way to resolve this
547     else
548         create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
549         expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
550         fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
551         # check for key expiration:
552         if [ "$expire" ]; then
553             if (( "$expire"  < "$curdate" )); then
554                 echo "! Host key is expired."
555                 # FIXME: recommend a way to resolve this other than re-keying?
556             elif (( "$expire" < "$warndate" )); then
557                 echo "! Host key expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)
558                 # FIXME: recommend a way to resolve this?
559             fi
560         fi
561
562         # and weirdnesses:
563         if [ "$create" ] && (( "$create" > "$curdate" )); then
564             echo "! Host key was created in the future(?!). Is your clock correct?"
565             echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
566         fi
567
568         # check for UserID expiration:
569         echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
570         while IFS=: read create expire uid ; do
571             # FIXME: should we be doing any checking on the form
572             # of the User ID?  Should we be unmangling it somehow?
573
574             if [ "$create" ] && (( "$create" > "$curdate" )); then
575                 echo "! User ID '$uid' was created in the future(?!).  Is your clock correct?"
576                 echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
577             fi
578             if [ "$expire" ] ; then
579                 if (( "$expire" < "$curdate" )); then
580                     echo "! User ID '$uid' is expired."
581                         # FIXME: recommend a way to resolve this
582                 elif (( "$expire" < "$warndate" )); then
583                     echo "! User ID '$uid' expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)                
584                     # FIXME: recommend a way to resolve this
585                 fi
586             fi
587         done
588             
589 # FIXME: verify that the host key is properly published to the
590 #   keyservers (do this with the non-privileged user)
591
592 # FIXME: check that there are valid, non-expired certifying signatures
593 #   attached to the host key after fetching from the public keyserver
594 #   (do this with the non-privileged user as well)
595
596 # FIXME: propose adding a revoker to the host key if none exist (do we
597 #   have a way to do that after key generation?)
598
599         # Ensure that the ssh_host_rsa_key file is present and non-empty:
600         echo
601         echo "Checking host SSH key..."
602         if [ ! -s "${VARLIB}/ssh_host_rsa_key" ] ; then
603             echo "! The host key as prepared for SSH (${VARLIB}/ssh_host_rsa_key) is missing or empty."
604         else
605             if [ $(stat -c '%a' "${VARLIB}/ssh_host_rsa_key") != 600 ] ; then
606                 echo "! Permissions seem wrong for ${VARLIB}/ssh_host_rsa_key -- should be 0600."
607             fi
608
609             # propose changes needed for sshd_config (if any)
610             if ! grep -q "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$" "$sshd_config"; then
611                 echo "! $sshd_config does not point to the monkeysphere host key (${VARLIB}/ssh_host_rsa_key)."
612                 echo " - Recommendation: add a line to $sshd_config: 'HostKey ${VARLIB}/ssh_host_rsa_key'"
613             fi
614             if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -q -v "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$") ; then
615                 echo "! $sshd_config refers to some non-monkeysphere host keys:"
616                 echo "$badhostkeys"
617                 echo " - Recommendation: remove the above HostKey lines from $sshd_config"
618             fi
619         fi
620     fi
621
622 # FIXME: look at the ownership/privileges of the various keyrings,
623 #    directories housing them, etc (what should those values be?  can
624 #    we make them as minimal as possible?)
625
626 # FIXME: look to see that the ownertrust rules are set properly on the
627 #    authentication keyring
628
629 # FIXME:  make sure that at least one identity certifier exists
630
631     echo
632     echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
633     # Ensure that User ID authentication is enabled:
634     if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$" "$sshd_config"; then
635         echo "! $sshd_config does not point to monkeysphere authorized keys."
636         echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${VARLIB}/authorized_keys/%u'"
637     fi
638     if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -q -v "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$") ; then
639         echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
640         echo "$badauthorizedkeys"
641         echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
642     fi
643 }
644
645 # retrieve key from web of trust, import it into the host keyring, and
646 # ltsign the key in the host keyring so that it may certify other keys
647 add_certifier() {
648     local domain
649     local trust
650     local depth
651     local keyID
652     local fingerprint
653     local ltsignCommand
654     local trustval
655
656     # set default values for trust depth and domain
657     domain=
658     trust=full
659     depth=1
660
661     # get options
662     TEMP=$(getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@")
663
664     if [ $? != 0 ] ; then
665         exit 1
666     fi
667
668     # Note the quotes around `$TEMP': they are essential!
669     eval set -- "$TEMP"
670
671     while true ; do
672         case "$1" in
673             -n|--domain)
674                 domain="$2"
675                 shift 2
676                 ;;
677             -t|--trust)
678                 trust="$2"
679                 shift 2
680                 ;;
681             -d|--depth)
682                 depth="$2"
683                 shift 2
684                 ;;
685             --)
686                 shift
687                 ;;
688             *)
689                 break
690                 ;;
691         esac
692     done
693
694     keyID="$1"
695     if [ -z "$keyID" ] ; then
696         failure "You must specify the key ID of a key to add."
697     fi
698     export keyID
699
700     # get the key from the key server
701     gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'"
702
703     # get the full fingerprint of a key ID
704     fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
705         grep '^fpr:' | grep "$keyID" | cut -d: -f10)
706
707     if [ -z "$fingerprint" ] ; then
708         failure "Key '$keyID' not found."
709     fi
710
711     echo
712     echo "key found:"
713     gpg_authentication "--fingerprint 0x${fingerprint}!"
714
715     echo "Are you sure you want to add the above key as a"
716     read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
717     if [ "${OK/y/Y}" != 'Y' ] ; then
718         failure "Identity certifier not added."
719     fi
720
721     # export the key to the host keyring
722     gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
723
724     if [ "$trust" == marginal ]; then
725         trustval=1
726     elif [ "$trust" == full ]; then
727         trustval=2
728     else
729         failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
730     fi
731
732     # ltsign command
733     # NOTE: *all* user IDs will be ltsigned
734     ltsignCommand=$(cat <<EOF
735 ltsign
736 y
737 $trustval
738 $depth
739 $domain
740 y
741 save
742 EOF
743         )
744
745     # ltsign the key
746     if echo "$ltsignCommand" | \
747         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
748
749         # update the trustdb for the authentication keyring
750         gpg_authentication "--check-trustdb"
751
752         echo
753         echo "Identity certifier added."
754     else
755         failure "Problem adding identify certifier."
756     fi
757 }
758
759 # delete a certifiers key from the host keyring
760 remove_certifier() {
761     local keyID
762     local fingerprint
763
764     keyID="$1"
765     if [ -z "$keyID" ] ; then
766         failure "You must specify the key ID of a key to remove."
767     fi
768
769     if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
770         read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
771         if [ "${OK/y/Y}" != 'Y' ] ; then
772             failure "Identity certifier not removed."
773         fi
774     else
775         failure
776     fi
777
778     # delete the requested key
779     if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
780         # delete key from host keyring as well
781         gpg_host --delete-key --batch --yes "0x${keyID}!"
782         # update the trustdb for the authentication keyring
783         gpg_authentication "--check-trustdb"
784
785         echo
786         echo "Identity certifier removed."
787     else
788         failure "Problem removing identity certifier."
789     fi
790 }
791
792 # list the host certifiers
793 list_certifiers() {
794     local keys
795     local key
796
797     # find trusted keys in authentication keychain
798     keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
799         grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
800
801     # output keys
802     for key in $keys ; do
803         gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
804     done
805 }
806
807 # issue command to gpg-authentication keyring
808 gpg_authentication_cmd() {
809     gpg_authentication "$@"
810 }
811
812 ########################################################################
813 # MAIN
814 ########################################################################
815
816 # unset variables that should be defined only in config file
817 unset KEYSERVER
818 unset AUTHORIZED_USER_IDS
819 unset RAW_AUTHORIZED_KEYS
820 unset MONKEYSPHERE_USER
821
822 # load configuration file
823 [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${ETC}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
824
825 # set empty config variable with ones from the environment, or with
826 # defaults
827 KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="subkeys.pgp.net"}}
828 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.config/monkeysphere/authorized_user_ids"}}
829 RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
830 MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
831
832 # other variables
833 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
834 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
835 GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${VARLIB}/gnupg-host"}
836 GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${VARLIB}/gnupg-authentication"}
837
838 # export variables needed in su invocation
839 export DATE
840 export MODE
841 export MONKEYSPHERE_USER
842 export KEYSERVER
843 export CHECK_KEYSERVER
844 export REQUIRED_USER_KEY_CAPABILITY
845 export GNUPGHOME_HOST
846 export GNUPGHOME_AUTHENTICATION
847 export GNUPGHOME
848
849 # get subcommand
850 COMMAND="$1"
851 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
852 shift
853
854 case $COMMAND in
855     'update-users'|'update-user'|'u')
856         update_users "$@"
857         ;;
858
859     'gen-key'|'g')
860         gen_key "$@"
861         ;;
862
863     'add-hostname'|'add-name'|'n+')
864         add_hostname "$@"
865         ;;
866
867     'revoke-hostname'|'revoke-name'|'n-')
868         revoke_hostname "$@"
869         ;;
870
871     'show-key'|'show'|'s')
872         show_server_key
873         ;;
874
875     'show-fingerprint'|'fingerprint'|'f')
876         fingerprint_server_key
877         ;;
878
879     'publish-key'|'publish'|'p')
880         publish_server_key
881         ;;
882
883     'diagnostics'|'d')
884         diagnostics
885         ;;
886
887     'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
888         add_certifier "$@"
889         ;;
890
891     'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
892         remove_certifier "$@"
893         ;;
894
895     'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
896         list_certifiers "$@"
897         ;;
898
899     'gpg-authentication-cmd')
900         gpg_authentication_cmd "$@"
901         ;;
902
903     '--help'|'help'|'-h'|'h'|'?')
904         usage
905         ;;
906
907     *)
908         failure "Unknown command: '$COMMAND'
909 Type '$PGRM help' for usage."
910         ;;
911 esac
912
913 exit "$RETURN"