included todo entry about porting monkeysphere to other OSes.
[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 --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 "----- 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     keyExpire=$(get_gpg_expiration "$keyExpire")
312
313     # set key parameters
314     keyParameters=$(cat <<EOF
315 Key-Type: $keyType
316 Key-Length: $keyLength
317 Key-Usage: $keyUsage
318 Name-Real: $userID
319 Expire-Date: $keyExpire
320 EOF
321 )
322
323     # add the revoker field if specified
324     # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
325     # FIXME: key is marked "sensitive"?  is this appropriate?
326     if [ "$revoker" ] ; then
327         keyParameters="${keyParameters}"$(cat <<EOF
328 Revoker: 1:$revoker sensitive
329 EOF
330 )
331     fi
332
333     echo "The following key parameters will be used for the host private key:"
334     echo "$keyParameters"
335
336     read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
337     if [ ${OK/y/Y} != 'Y' ] ; then
338         failure "aborting."
339     fi
340
341     # add commit command
342     keyParameters="${keyParameters}"$(cat <<EOF
343
344 %commit
345 %echo done
346 EOF
347 )
348
349     log "generating server key..."
350     echo "$keyParameters" | gpg_host --batch --gen-key
351
352     # output the server fingerprint
353     fingerprint_server_key "=${userID}"
354
355     # find the key fingerprint of the newly generated key
356     fingerprint=$(fingerprint_server_key)
357
358     # export host ownertrust to authentication keyring
359     log "setting ultimate owner trust for server key..."
360     echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
361
362     # translate the private key to ssh format, and export to a file
363     # for sshs usage.
364     # NOTE: assumes that the primary key is the proper key to use
365     (umask 077 && \
366         gpg_host --export-secret-key "$fingerprint" | \
367         openpgp2ssh "$fingerprint" > "${VARLIB}/ssh_host_rsa_key")
368     log "Private SSH host key output to file: ${VARLIB}/ssh_host_rsa_key"
369 }
370
371 # extend the lifetime of a host key:
372 extend_key() {
373     local fpr=$(fingerprint_server_key)
374     local extendTo="$1"
375
376     if [ -z "$fpr" ] ; then
377         failure "You don't appear to have a MonkeySphere host key on this server.  Try 'monkeysphere-server gen-key' first."
378     fi
379
380     # get the new expiration date
381     extendTo=$(get_gpg_expiration "$extendTo")
382
383     gpg_host --quiet --command-fd 0 --edit-key "$fpr" <<EOF 
384 expire
385 $extendTo
386 save
387 EOF
388
389     echo
390     echo "NOTE: Host key expiration date adjusted, but not yet published."
391     echo "Run '$PGRM publish-key' to publish the new expiration date."
392 }
393
394 # add hostname user ID to server key
395 add_hostname() {
396     local userID
397     local fingerprint
398     local tmpuidMatch
399     local line
400     local adduidCommand
401
402     if [ -z "$1" ] ; then
403         failure "You must specify a hostname to add."
404     fi
405
406     userID="ssh://${1}"
407
408     fingerprint=$(fingerprint_server_key)
409
410     # match to only ultimately trusted user IDs
411     tmpuidMatch="u:$(echo $userID | gpg_escape)"
412
413     # find the index of the requsted user ID
414     # NOTE: this is based on circumstantial evidence that the order of
415     # this output is the appropriate index
416     if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
417         | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
418         failure "Host userID '$userID' already exists."
419     fi
420
421     echo "The following user ID will be added to the host key:"
422     echo "  $userID"
423     read -p "Are you sure you would like to add this user ID? (y/N) " OK; OK=${OK:=N}
424     if [ ${OK/y/Y} != 'Y' ] ; then
425         failure "User ID not added."
426     fi
427
428     # edit-key script command to add user ID
429     adduidCommand=$(cat <<EOF
430 adduid
431 $userID
432
433
434 save
435 EOF
436         )
437
438     # execute edit-key script
439     if echo "$adduidCommand" | \
440         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
441
442         # update the trustdb for the authentication keyring
443         gpg_authentication "--check-trustdb"
444
445         show_server_key
446
447         echo
448         echo "NOTE: User ID added to key, but key not published."
449         echo "Run '$PGRM publish-key' to publish the new user ID."
450     else
451         failure "Problem adding user ID."
452     fi
453 }
454
455 # revoke hostname user ID to server key
456 revoke_hostname() {
457     local userID
458     local fingerprint
459     local tmpuidMatch
460     local line
461     local uidIndex
462     local message
463     local revuidCommand
464
465     if [ -z "$1" ] ; then
466         failure "You must specify a hostname to revoke."
467     fi
468
469     echo "WARNING: There is a known bug in this function."
470     echo "This function has been known to occasionally revoke the wrong user ID."
471     echo "Please see the following bug report for more information:"
472     echo "http://web.monkeysphere.info/bugs/revoke-hostname-revoking-wrong-userid/"
473     read -p "Are you sure you would like to proceed? (y/N) " OK; OK=${OK:=N}
474     if [ ${OK/y/Y} != 'Y' ] ; then
475         failure "aborting."
476     fi
477
478     userID="ssh://${1}"
479
480     fingerprint=$(fingerprint_server_key)
481
482     # match to only ultimately trusted user IDs
483     tmpuidMatch="u:$(echo $userID | gpg_escape)"
484
485     # find the index of the requsted user ID
486     # NOTE: this is based on circumstantial evidence that the order of
487     # this output is the appropriate index
488     if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
489         | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
490         uidIndex=${line%%:*}
491     else
492         failure "No non-revoked user ID '$userID' is found."
493     fi
494
495     echo "The following host key user ID will be revoked:"
496     echo "  $userID"
497     read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
498     if [ ${OK/y/Y} != 'Y' ] ; then
499         failure "User ID not revoked."
500     fi
501
502     message="Hostname removed by monkeysphere-server $DATE"
503
504     # edit-key script command to revoke user ID
505     revuidCommand=$(cat <<EOF
506 $uidIndex
507 revuid
508 y
509 4
510 $message
511
512 y
513 save
514 EOF
515         )       
516
517     # execute edit-key script
518     if echo "$revuidCommand" | \
519         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
520
521         # update the trustdb for the authentication keyring
522         gpg_authentication "--check-trustdb"
523
524         show_server_key
525
526         echo
527         echo "NOTE: User ID revoked, but revocation not published."
528         echo "Run '$PGRM publish-key' to publish the revocation."
529     else
530         failure "Problem revoking user ID."
531     fi
532 }
533
534 # publish server key to keyserver
535 publish_server_key() {
536     read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
537     if [ ${OK/y/Y} != 'Y' ] ; then
538         failure "key not published."
539     fi
540
541     # find the key fingerprint
542     fingerprint=$(fingerprint_server_key)
543
544     # publish host key
545     gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
546 }
547
548 diagnostics() {
549 #  * check on the status and validity of the key and public certificates
550     local seckey
551     local keysfound
552     local curdate
553     local warnwindow
554     local warndate
555     local create
556     local expire
557     local uid
558     local fingerprint
559     local badhostkeys
560     local sshd_config
561
562     # FIXME: what's the correct, cross-platform answer?
563     sshd_config=/etc/ssh/sshd_config
564     seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
565     keysfound=$(echo "$seckey" | grep -c ^sec:)
566     curdate=$(date +%s)
567     # warn when anything is 2 months away from expiration
568     warnwindow='2 months'
569     warndate=$(date +%s -d "$warnwindow")
570
571     echo "Checking host GPG key..."
572     if (( "$keysfound" < 1 )); then
573         echo "! No host key found."
574         echo " - Recommendation: run 'monkeysphere-server gen-key'"
575     elif (( "$keysfound" > 1 )); then
576         echo "! More than one host key found?"
577         # FIXME: recommend a way to resolve this
578     else
579         create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
580         expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
581         fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
582         # check for key expiration:
583         if [ "$expire" ]; then
584             if (( "$expire"  < "$curdate" )); then
585                 echo "! Host key is expired."
586                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
587             elif (( "$expire" < "$warndate" )); then
588                 echo "! Host key expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)
589                 echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
590             fi
591         fi
592
593         # and weirdnesses:
594         if [ "$create" ] && (( "$create" > "$curdate" )); then
595             echo "! Host key was created in the future(?!). Is your clock correct?"
596             echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
597         fi
598
599         # check for UserID expiration:
600         echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
601         while IFS=: read create expire uid ; do
602             # FIXME: should we be doing any checking on the form
603             # of the User ID?  Should we be unmangling it somehow?
604
605             if [ "$create" ] && (( "$create" > "$curdate" )); then
606                 echo "! User ID '$uid' was created in the future(?!).  Is your clock correct?"
607                 echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
608             fi
609             if [ "$expire" ] ; then
610                 if (( "$expire" < "$curdate" )); then
611                     echo "! User ID '$uid' is expired."
612                         # FIXME: recommend a way to resolve this
613                 elif (( "$expire" < "$warndate" )); then
614                     echo "! User ID '$uid' expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F)                
615                     # FIXME: recommend a way to resolve this
616                 fi
617             fi
618         done
619             
620 # FIXME: verify that the host key is properly published to the
621 #   keyservers (do this with the non-privileged user)
622
623 # FIXME: check that there are valid, non-expired certifying signatures
624 #   attached to the host key after fetching from the public keyserver
625 #   (do this with the non-privileged user as well)
626
627 # FIXME: propose adding a revoker to the host key if none exist (do we
628 #   have a way to do that after key generation?)
629
630         # Ensure that the ssh_host_rsa_key file is present and non-empty:
631         echo
632         echo "Checking host SSH key..."
633         if [ ! -s "${VARLIB}/ssh_host_rsa_key" ] ; then
634             echo "! The host key as prepared for SSH (${VARLIB}/ssh_host_rsa_key) is missing or empty."
635         else
636             if [ $(stat -c '%a' "${VARLIB}/ssh_host_rsa_key") != 600 ] ; then
637                 echo "! Permissions seem wrong for ${VARLIB}/ssh_host_rsa_key -- should be 0600."
638             fi
639
640             # propose changes needed for sshd_config (if any)
641             if ! grep -q "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$" "$sshd_config"; then
642                 echo "! $sshd_config does not point to the monkeysphere host key (${VARLIB}/ssh_host_rsa_key)."
643                 echo " - Recommendation: add a line to $sshd_config: 'HostKey ${VARLIB}/ssh_host_rsa_key'"
644             fi
645             if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -q -v "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$") ; then
646                 echo "! $sshd_config refers to some non-monkeysphere host keys:"
647                 echo "$badhostkeys"
648                 echo " - Recommendation: remove the above HostKey lines from $sshd_config"
649             fi
650         fi
651     fi
652
653 # FIXME: look at the ownership/privileges of the various keyrings,
654 #    directories housing them, etc (what should those values be?  can
655 #    we make them as minimal as possible?)
656
657 # FIXME: look to see that the ownertrust rules are set properly on the
658 #    authentication keyring
659
660 # FIXME:  make sure that at least one identity certifier exists
661
662     echo
663     echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
664     # Ensure that User ID authentication is enabled:
665     if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$" "$sshd_config"; then
666         echo "! $sshd_config does not point to monkeysphere authorized keys."
667         echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${VARLIB}/authorized_keys/%u'"
668     fi
669     if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -q -v "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$") ; then
670         echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
671         echo "$badauthorizedkeys"
672         echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
673     fi
674 }
675
676 # retrieve key from web of trust, import it into the host keyring, and
677 # ltsign the key in the host keyring so that it may certify other keys
678 add_certifier() {
679     local domain
680     local trust
681     local depth
682     local keyID
683     local fingerprint
684     local ltsignCommand
685     local trustval
686
687     # set default values for trust depth and domain
688     domain=
689     trust=full
690     depth=1
691
692     # get options
693     TEMP=$(getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@")
694
695     if [ $? != 0 ] ; then
696         exit 1
697     fi
698
699     # Note the quotes around `$TEMP': they are essential!
700     eval set -- "$TEMP"
701
702     while true ; do
703         case "$1" in
704             -n|--domain)
705                 domain="$2"
706                 shift 2
707                 ;;
708             -t|--trust)
709                 trust="$2"
710                 shift 2
711                 ;;
712             -d|--depth)
713                 depth="$2"
714                 shift 2
715                 ;;
716             --)
717                 shift
718                 ;;
719             *)
720                 break
721                 ;;
722         esac
723     done
724
725     keyID="$1"
726     if [ -z "$keyID" ] ; then
727         failure "You must specify the key ID of a key to add."
728     fi
729     export keyID
730
731     # get the key from the key server
732     gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'"
733
734     # get the full fingerprint of a key ID
735     fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
736         grep '^fpr:' | grep "$keyID" | cut -d: -f10)
737
738     if [ -z "$fingerprint" ] ; then
739         failure "Key '$keyID' not found."
740     fi
741
742     echo
743     echo "key found:"
744     gpg_authentication "--fingerprint 0x${fingerprint}!"
745
746     echo "Are you sure you want to add the above key as a"
747     read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
748     if [ "${OK/y/Y}" != 'Y' ] ; then
749         failure "Identity certifier not added."
750     fi
751
752     # export the key to the host keyring
753     gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
754
755     if [ "$trust" == marginal ]; then
756         trustval=1
757     elif [ "$trust" == full ]; then
758         trustval=2
759     else
760         failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
761     fi
762
763     # ltsign command
764     # NOTE: *all* user IDs will be ltsigned
765     ltsignCommand=$(cat <<EOF
766 ltsign
767 y
768 $trustval
769 $depth
770 $domain
771 y
772 save
773 EOF
774         )
775
776     # ltsign the key
777     if echo "$ltsignCommand" | \
778         gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
779
780         # update the trustdb for the authentication keyring
781         gpg_authentication "--check-trustdb"
782
783         echo
784         echo "Identity certifier added."
785     else
786         failure "Problem adding identify certifier."
787     fi
788 }
789
790 # delete a certifiers key from the host keyring
791 remove_certifier() {
792     local keyID
793     local fingerprint
794
795     keyID="$1"
796     if [ -z "$keyID" ] ; then
797         failure "You must specify the key ID of a key to remove."
798     fi
799
800     if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
801         read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
802         if [ "${OK/y/Y}" != 'Y' ] ; then
803             failure "Identity certifier not removed."
804         fi
805     else
806         failure
807     fi
808
809     # delete the requested key
810     if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
811         # delete key from host keyring as well
812         gpg_host --delete-key --batch --yes "0x${keyID}!"
813
814         # update the trustdb for the authentication keyring
815         gpg_authentication "--check-trustdb"
816
817         echo
818         echo "Identity certifier removed."
819     else
820         failure "Problem removing identity certifier."
821     fi
822 }
823
824 # list the host certifiers
825 list_certifiers() {
826     local keys
827     local key
828
829     # find trusted keys in authentication keychain
830     keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
831         grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
832
833     # output keys
834     for key in $keys ; do
835         gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
836     done
837 }
838
839 # issue command to gpg-authentication keyring
840 gpg_authentication_cmd() {
841     gpg_authentication "$@"
842 }
843
844 ########################################################################
845 # MAIN
846 ########################################################################
847
848 # unset variables that should be defined only in config file
849 unset KEYSERVER
850 unset AUTHORIZED_USER_IDS
851 unset RAW_AUTHORIZED_KEYS
852 unset MONKEYSPHERE_USER
853
854 # load configuration file
855 [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${ETC}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
856
857 # set empty config variable with ones from the environment, or with
858 # defaults
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 KEYSERVER
875 export CHECK_KEYSERVER
876 export REQUIRED_USER_KEY_CAPABILITY
877 export GNUPGHOME_HOST
878 export GNUPGHOME_AUTHENTICATION
879 export GNUPGHOME
880
881 # get subcommand
882 COMMAND="$1"
883 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
884 shift
885
886 case $COMMAND in
887     'update-users'|'update-user'|'u')
888         update_users "$@"
889         ;;
890
891     'gen-key'|'g')
892         gen_key "$@"
893         ;;
894
895     'extend-key'|'e')
896         extend_key "$@"
897         ;;
898
899     'add-hostname'|'add-name'|'n+')
900         add_hostname "$@"
901         ;;
902
903     'revoke-hostname'|'revoke-name'|'n-')
904         revoke_hostname "$@"
905         ;;
906
907     'show-key'|'show'|'s')
908         show_server_key
909         ;;
910
911     'publish-key'|'publish'|'p')
912         publish_server_key
913         ;;
914
915     'diagnostics'|'d')
916         diagnostics
917         ;;
918
919     'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
920         add_certifier "$@"
921         ;;
922
923     'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
924         remove_certifier "$@"
925         ;;
926
927     'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
928         list_certifiers "$@"
929         ;;
930
931     'gpg-authentication-cmd')
932         gpg_authentication_cmd "$@"
933         ;;
934
935     '--help'|'help'|'-h'|'h'|'?')
936         usage
937         ;;
938
939     *)
940         failure "Unknown command: '$COMMAND'
941 Type '$PGRM help' for usage."
942         ;;
943 esac
944
945 exit "$RETURN"