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