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