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