#!/bin/bash

# Lizenz / license:
# GPL v3 or later: http://www.gnu.org/licenses/gpl-3.0

# Documentation:
# http://www.openpgp-schulungen.de/scripte/keygeneration/ (German)

# CHANGELOG
# Hauke Laging, hauke.laging (emailzeichen) openpgp-schulungen.de
#	XMPP: gnupg-support@jabber.org (OTR: http://www.hauke-laging.de/sicherheit/#mein_otr)
### $VERSION nicht vergessen!
# 4.5, 21.12.2015, Hauke Laging
VERSION=4.5

set -a

: ${DEBUG:=no}
if [ yes = "$DEBUG" ]; then
	echo "ACHTUNG: Script läuft im Debug-Modus!"
	echo "Weiter mit Return..."
	read cont
	set -vx
fi

E_PREPARATION=3
E_NOCONFIGFILE=4
E_GPGCONFEXISTS=5 # ~/.gnupg
E_UIDERROR=6
E_MAINKEYGEN=7
E_GPGCONFNOTEXISTS=8
E_NOTEMPLATEFILE=9
E_FPR=10
E_MODIFYGPGCONFIG=11
E_FUNCPARAM=12
E_PARAM=13
E_KEYNOTFOUND=14
E_NOSECMAINKEY=15
E_GPGCONFRENAME=16 # ~/.gnupg
E_LCKEYGEN=17
E_LCKEYFPR=18

E_NONROOT=50

# Konstanten
DOCURL=http://www.openpgp-schulungen.de/scripte/keygeneration/
CONFIGFILE=gpg.conf
CONFIGFILE_TMP1=gpg.conf-tmp1
CONFIGFILE_TMP2=gpg.conf-tmp2
MODIFICATIONSTARTLINE="### BEGINMODIFY"
KEYCONFIGFILE=key-generation.conf
KEYGENERATIONFILE=tmp-keytemplate
LCKEYGENERATIONFILE=tmp-lckeytemplate
LOGFILE=status.log
LCKEYLOGFILE=lckey-status.log
COPYFILESTODIR=targetdir
MODIFY=ANPASSEN
SCRIPTCOPYNAME=key-generation.sh
MAINKEYLENGTH_NORMAL=4096
SUBKEYLENGTH_NORMAL=2048
MAINKEYLENGTH_HISEC=4096
SUBKEYLENGTH_HISEC=4096
KNOWNWORKINGGPGVERSIONS=(1.4.12 2.0.19)
GENERATIONPASSPHRASE=foo # wird bei der Schlüsselerzeugung verwendet (sinnlos, davon abzuweichen)
if [ -z "$PASSPHRASE" ]; then
	passphrase_entered=no
else
	passphrase_entered=yes
fi
: ${PASSPHRASE:="$GENERATIONPASSPHRASE"} # abweichende Passphrase kann auf diese Weise übergeben werden
EXPORTPASSPHRASE="$PASSPHRASE" # wird beim Export verwendet, also potentiell bei einem Schlüssel, der nicht vom Script generiert wurde und eine andere Passphrase hat
# SIGADVERTISINGSUBKEY=(--cert-notation keygeneration_de@openpgp-notations.org="Dieser Unterschlüssel wurde mit Hilfe von www.openpgp-schulungen.de erzeugt.")
# SIGADVERTISINGUID=(--cert-notation keygeneration_de@openpgp-notations.org="Diese User-ID wurde mit Hilfe von www.openpgp-schulungen.de erzeugt.")
SIGADVERTISINGSUBKEY=(--expert) # dummy
SIGADVERTISINGUID=(--expert)    # dummy
CERTKEYSDEFAULTDIR=lsign_keys
DEFAULT_TARGET_SUBDIR=export
CLEARSIGNDEFAULTSUBDIR=clearsign_plaintext
DETACHSIGNDEFAULTSUBDIR=detachsign_data
LCKEYFILE_SECRET="lckey.secret.asc"
OWNERTRUSTEXPORTFILE=ownertrust.txt
KEYFILE_SECRET_MAINKEY_TMP=key-tmp.secret-mainkey.gpg
KEYFILE_PUBLIC_TMP=key-tmp.public.asc
EXPORT_OPENPGP_DIR_MAIN="_gnupg-mainkey-only"
EXPORT_OPENPGP_DIR_SUB="_gnupg-mainkey+subkeys"
EXPIRE_PERIOD_NORMAL=2y
EXPIRE_PERIOD_HISEC=6m
SESSIONNAME="keygeneration"
WINDOW_NAME_MAIN="main"
WINDOW_NAME_COLLECT="collect"
LCKEYFILE_PUBLIC="lckey.public.asc"
TARGET_SUBDIR_IMPORT="import"
TARGET_SUBDIR_SECENV="secure-environment-only"
TARGET_SUBDIR_CERTS="certs"
TARGET_SUBDIR_SIGS="sigs"
TARGET_SUBDIR_MISC="misc"
TMP_DIR_USER="/tmp/${USER}/entropy"
COLLECTENTROPY_PID_FILE="collect_entropy.pid"
IMPORT_CERTS_WIN="import_certs.bat"
IMPORT_CERTS_UNIX="import_certs.sh"
IMPORT_WIN="import_win.bat"
IMPORT_UNIX="import_unix.sh"
TEXTSIGNATURE_FILE="textsignatur.txt"
AGENT_CONFIGFILE="gpg-agent.conf"
INFO_UID="yes" # skip setting the primary UID if there are two UIDs
SHORTID_FILE_TEXT="Diese Datei mit der short ID als Name gehoert zum Windows-Importscript."
DEFAULT_PASSPHRASE_LENGTH=18

# global relevante Laufzeitvariablen
secmainkeyneeded=yes # ist z.B. für --adapt-config egal
timechange=yes
startline_number=0
# Wenn Netzwerkverbindungen anderswohin als zum localhost bestehen, ist das System unsicher
networkconnections=
systemknownunsafe=no
if type gpg2 &>/dev/null; then
	gpg_cmd=gpg2
else
	gpg_cmd=gpg
fi
startpath="${0%/*}"
test -n "$startpath" -a -d "$startpath" || exit 1
startpath="$(readlink --no-newline -f "$startpath"; echo x)"
startpath="${startpath%x}"
passphrase_length="$DEFAULT_PASSPHRASE_LENGTH"

# Laufzeitvariablen - später belegt
fingerprint=
x_fingerprint=
short_id=
long_id=
x_short_id=
x_long_id=
keyfile_secret_mainkey=
keyfile_secret_mainkey_only=
keyfile_secret_subkeys=
keyfile_public=
keyfile_public_mainkeyonly=
dont_restore_systemtime=no
mode= # all | cert | sign | adapt-config | export
if [ "yes" = "$LESS_INFO_AND_QUESTIONS" ]; then
	verbose="no"
else
	verbose="yes"
fi
mainkeylength="$MAINKEYLENGTH_NORMAL"
subkeylength="$SUBKEYLENGTH_NORMAL"
if [ "root" = "$(whoami)" ]; then
	is_root=yes
else
	is_root=no
fi
expire_period="$EXPIRE_PERIOD_NORMAL"
keyid= # if a key ID has been passed (for other operations than key generation)
seedfile= # optional
yes_char="j"
no_char="n"
securitylevel="normal" # everyday key (secure mainkey, insecure subkeys) vs. high-security key
if type gpg-agent &>/dev/null; then
	gpgagent_installed="yes"
else
	gpgagent_installed="no"
fi
gpgagent_running="no"
gpgagent_pid=
if type gpg-preset-passphrase &>/dev/null; then
	gpgpresetpassphrase_installed="yes"
else
	gpgpresetpassphrase_installed="no"
fi

if [[ $- =~ v ]]; then
	debug_v="yes" # debug help for bash -v
else
	debug_v="no"
fi
if [[ $- =~ x ]]; then
	debug_x="yes" # debug help for bash -x
else
	debug_x="no"
fi

# nur lokal relevante Laufzeitvariablen
parameter=
tmpvar= # wenn mal kurz lokal eine Variable gebraucht wird

shopt -s extglob
shopt -s nullglob
set +H

script_info () {
	cat <<EOT
key-generation.sh Version ${VERSION}
Dokumentation unter:
	${DOCURL}

Dies ist ein Script von www.openpgp-schulungen.de zur Erzeugung guter 
OpenPGP-Schlüssel mit Hilfe von GnuPG. Dieses Script sollte nur in einer 
sicheren Umgebung (z.B. Knoppix oder ein anderes Live-System ohne 
Netzwerkanbindung) aufgerufen werden.
EOT
} # script_info ()

usage () {
	cat <<EOT
'$0' [Optionen] [ --help | --all | --cert [<ID>] | --expire [<ID>] |
	--export [<ID>] | --sign [<ID>] | --adapt-config [<ID>] |
	--passphrase ]

Standardoperation: --all

Optionen
========
  --high-security :
      Größere Schlüssellängen, kürzerer Gültigkeitszeitraum
  --seed-file <file> :
      Vorbereitete Seed-Datei, damit weniger Entropie verbraucht wird
      

Kommandos
=========
  ohne Kommando   :
      das volle Programm
  --help          :
      diese Ausgabe
  --cert          :
      Zertifizierungen erstellen
  --sign          :
      (Hauptschlüssel-)Signaturen erstellen
  --adapt-config  :
      ID / Fingerprint in gpg.conf schreiben
  --export        :
      private Haupt- und Unterschlüssel getrennt exportieren (für einen 
      Offline-Hauptschlüssel)
  --passphrase    :
      Passphrase-Generierung

ID muss die short ID, die long ID oder der Fingerprint sein, mit oder ohne 
0x-Präfix. Die ID muss angegeben werden, falls mehr als ein geheimer 
Hauptschlüssel vorhanden ist.

Für --export und --script muss die Passphrase auf $GENERATIONPASSPHRASE gesetzt 
sein. Eine abweichende Passphrase kann in der Umgebungsvariable PASSPHRASE 
übergeben werden.
EOT
}

if [ --help = "$1" ]; then
	script_info
	echo
	usage
	exit 0
fi

###################################################################################################

yesno () {
	test $# -ne 1 && { echo "internal error in yesno(); aborting."; exit 1; }
	local text="$1" answer
	while true; do
		echo -n "${text} (${yes_char}/${no_char}): "
		read answer
		if [ "$yes_char" = "$answer" -o "$no_char" = "$answer" ]; then
			if [ "$yes_char" = "$answer" ]; then
				return 0
			else
				return 1
			fi
		else
			echo "Falsche Eingabe; bitte noch mal..."
			echo
			echo
			echo
		fi
		
	done
} # yesno ()

###################################################################################################
###################################################################################################
###################################################################################################

clear () {
	if [ yes = "$DEBUG" ]; then
		# im Debug-Modus clear ignorieren; nervt nur
		echo "######################################"
		echo "DEBUG-Modus: clear ignoriert"
		echo "######################################"
	else
		command clear
	fi
} # clear ()

###################################################################################################
###################################################################################################
###################################################################################################

set_startline_number () {
	# Diese Funktion ermittelt, in welcher Zeile (von $CONFIGFILE) der Wert von $MODIFICATIONSTARTLINE steht.
	# Ist keine solche Zeile vorhanden, wird der Wert auf 0 gesetzt. Ansonsten wird ab 1 gezählt.
	startline_number=$(awk -v startline="$MODIFICATIONSTARTLINE" -v startline_number=0 '$0 == startline {startline_number=NR;exit}; END {print startline_number}' "$CONFIGFILE")
	: DEBUG: startline_number = $startline_number
} # set_startline_number ()

###################################################################################################
###################################################################################################
###################################################################################################

cat_config_for_modify () {
	# Diese Funktion gibt den relevanten Teil der GnuPG-Konfigurationsdatei aus, 
	# also den unterhalb von $MODIFICATIONSTARTLINE, wenn vorhanden, ansonsten 
	# die ganze Datei.
	if [ 0 -eq "$startline_number" ]; then
		cat "$CONFIGFILE"
	else
		awk -v startline_number="$startline_number" 'NR > startline_number {print}' "$CONFIGFILE"
	fi
} # cat_config_for_modify ()

###################################################################################################
###################################################################################################
###################################################################################################

wipe_file () {
	local rc
	if [ $# -ne 1 ]; then
		echo "wipe: falsche Parameteranzahl ($#); Abbruch"
		exit $E_FUNCPARAM
	fi
	if ! [ -f "$1" ]; then
		echo "wipe: Parameter ('${1}') ist keine Datei; Abbruch"
		exit $E_FUNCPARAM
	fi
	if [ yes = "$DEBUG" ]; then
		dd if=/dev/zero of="$1" bs=50k count=1
		rc=$?
		if [ 0 -eq "$rc" ]; then
			rm -f -- "$1"
		fi
		# COMMENT: Das Überschreiben ist wichtig, rm nur Kosmetik. Deshalb den exit code von dd zurückgeben, nicht den von rm
		return "$rc"
	else
		dd if=/dev/zero of="$1" bs=50k count=1 &>/dev/null
		rc=$?
		if [ 0 -eq "$rc" ]; then
			rm -f -- "$1" &>/dev/null
		fi
		# COMMENT: Das Überschreiben ist wichtig, rm nur Kosmetik. Deshalb den exit code von dd zurückgeben, nicht den von rm
		return "$rc"
	fi
} # wipe_file ()

###################################################################################################
###################################################################################################
###################################################################################################

cmp_files () {
	# COMMENT: Vorteil dieser Funktion gegenüber z.B. cmp: Sie braucht nur gpg, 
	#	und das ist garantiert vorhanden.
	test $# -eq 2 || exit 1
	local file1="$1" file2="$2" hash1 hash2
	test -f "$file1" || exit 1
	test -f "$file2" || exit 1
	hash1=$(gpg --print-md SHA1 <"$file1")
	hash2=$(gpg --print-md SHA1 <"$file2")
	if [[ "$hash1" == "$hash2" ]]; then
		return 0
	else
		return 1
	fi
} # cmp_files ()

###################################################################################################
###################################################################################################
###################################################################################################

unique_uid () {
	local count=${#existing_uids[@]} i
	if [ $# -ne 1 ]; then
		echo "interner Fehler; Abbruch"
		exit $E_FUNCPARAM
	fi
	new_uid="$1"
	for((i=0;i<count;i++)); do
		if [ x"$new_uid" = x"${existing_uids[i]}" ]; then
			return 1
		fi
	done
	return 0
} # unique_uid ()

###################################################################################################
###################################################################################################
###################################################################################################

check_keyconfigfile () {
	bash -c ". '${KEYCONFIGFILE}'"
} # check_keyconfigfile ()

###################################################################################################
###################################################################################################
###################################################################################################

start_agent () {
	echo
	echo "Starte gpg-agent..."
	# Problem: Knoppix enthält gpg-agent nicht
} # start_agent ()

###################################################################################################
###################################################################################################
###################################################################################################

# HINT: when applying changes don't forget the separate passphrase script

print_passphrase () {
	local length="$1" i char
	if [ -z "$length" ]; then
		length="$passphrase_length"
	fi
	if ! [[ $length =~ [1-9][0-9]* ]]; then
		length="$passphrase_length"
	fi
	# COMMENT: We need more input than output as some chars are ignored
	gpg --armor --gen-random 1 $((length*2)) | 
		(i=0
			while read -n 1 char; do
				if [ "$length" -le "$i" ]; then
					break
				fi
				# COMMENT: nicht alle Zeichen: "k", "K", "l", "I", "1", "O", und "0" wegen Verwechselungsgefahr weglassen
				if [[ $char =~ [abcdefghijmnopqrstuvwxyzABCDEFGHJLMNPQRSTUVWXYZ23456789] ]]; then
					echo -n "$char"
					((i++))
				fi
			done )
} # print_passphrase ()

###################################################################################################
###################################################################################################
###################################################################################################

trap_sigint () {
	echo
	echo "Strg-C wurde deaktiviert. Beenden aus anderer Konsole mit: kill $$"
	echo
} # trap_sigint ()

###################################################################################################
###################################################################################################
###################################################################################################

screenswitch_collect () {
	if [ yes = "$USE_SCREEN" ]; then
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Wenn Sie jetzt Return drücken, beginnt die Generierung eines (weiteren) 
(Unter-)Schlüssels. Für die Erzeugung von Zufallszahlen wird zu einem 
andere Programm umgeschaltet, sobald Sie Return drücken.

Wenn genügend Zufallszahlen erzeugt wurden, wird (nach Ankündigung) 
automatisch zu diesem Script zurückgeschaltet.

Weiter mit Return...
EOT
		read cont
		screen -S "$SESSIONNAME" -X select "$WINDOW_NAME_COLLECT"
		screenswitchback_collectentropy
	fi
} # screenswitch_collect ()

###################################################################################################
###################################################################################################
###################################################################################################

screenswitchback_collectentropy () {
	if [ yes = "$USE_SCREEN" ]; then
		local pid_file="${TMP_DIR_USER}/${COLLECTENTROPY_PID_FILE}" pid
		test -f "$pid_file" || return 1
		pid="$(<"$pid_file")"
		if [ -n "$pid" ]; then
			kill -USR1 "$pid"
			return $?
		else
			return 1
		fi
		# TODO: collect_entropy.sh anpassen, so dass der Wechsel per Signal sofort eingeleitet werden kann
		# screen -S "$SESSIONNAME" -X select "$WINDOW_NAME_MAIN"
		
	fi
} # screenswitchback_collectentropy ()

###################################################################################################
###################################################################################################
###################################################################################################

gpgagent_start () {
	# FIXME
	GPG_AGENT_INFO=
	export GPG_AGENT_INFO
	return 0
	if [ "yes" = "$gpgagent_installed" ]; then
		eval $(gpg-agent --daemon 2>/dev/null)
		if gpg-connect-agent /bye &>/dev/null; then
			gpgagent_running="yes"
			echo "gpg-agent started" >&2
			if [[ $GPG_AGENT_INFO =~ :[0-9]+: ]]; then
				gpgagent_pid="${GPG_AGENT_INFO%:*}"
				gpgagent_pid="${gpgagent_pid#*:}"
			fi
		else
			echo "gpg-agent could not be started" >&2
		fi
	fi
} # gpgagent_start ()

###################################################################################################
###################################################################################################
###################################################################################################

gpgagent_reset () {
	if [ "yes" = "$gpgagent_installed" ]; then
		if [ -n "$gpgagent_pid" ]; then
			kill -HUP "$gpgagent_pid"
		fi
	fi
} # gpgagent_reset ()

###################################################################################################
###################################################################################################
###################################################################################################

gpg-preset-passphrase () {
	local full_path
	# This function is necessary because gpg-preset-passphrase is not in PATH and thus must be called directly
	# /usr/lib64/gpg-preset-passphrase : openSUSE 13.1 (x86_64)
	# /usr/lib/gnupg2/gpg-preset-passphrase : Microknoppix 7.2
	for full_path in /usr/lib64/gpg-preset-passphrase /usr/lib/gnupg2/gpg-preset-passphrase; do
		if [ -x  "$full_path" ]; then
			"$full_path" "$@"
			return 0
		fi
	done
	cat &>/dev/null
} # gpg-preset-passphrase ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__verbosity () {
	if [ "yes" != "$LESS_INFO_AND_QUESTIONS" -a "no" != "$LESS_INFO_AND_QUESTIONS" ]; then
		clear
		cat <<EOT
AKTION ERFORDERLICH: Umfang der Textausgabe und Abfragen
======================================================================

Möchten Sie, dass dieses Script Meldungen ausgibt, die lediglich informativ, 
aber nicht zwingend erforderlich sind, und verzichtbare Nachfragen macht?

Dies ist typischerweise sinnvoll, wenn Sie dieses Script ohne Hilfe durch 
Dritte ausführen, aber wenn Sie in einer Schulung sitzen, können Sie darauf 
verzichten, um Zeit zu sparen.

EOT
		if yesno "Optionale Informationen ausgeben?"; then
			verbose="yes"
			LESS_INFO_AND_QUESTIONS="no"
		else
			verbose="no"
			LESS_INFO_AND_QUESTIONS="yes"
		fi
	else
		if [ "yes" = "$LESS_INFO_AND_QUESTIONS" ]; then
			verbose="no"
		else
			verbose="yes"
		fi
	fi

	set +x
	set +v
} # sub__verbosity ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__get_key () {
	local id="$1" length hexpart x_id idtype output fingerprint count_main count_sub count long_id short_id
	id="${id// /}"
	if [ -n "$id" ]; then
		length=${#id}
		case "$length" in
			10|18|42)
				if [[ ! $id =~ ^0x[0123456789aAbBcCdDeEfF]*$ ]]; then
					return 1
				fi
				hexpart="${id:2}"
				x_id="$id"
			;;
			8|16|40)
				if [[ ! $id =~ ^[0123456789aAbBcCdDeEfF]*$ ]]; then
					return 1
				fi
				hexpart="$id"
				x_id="0x${id}"
			;;
			*)
				# COMMENT: illegal ID
				return 2
			;;
		esac
		case "$length" in
			8|10)
				idtype=short
			;;
			16|18)
				idtype=long
			;;
			40|42)
				idtype=fpr
			;;
		esac
		case "$idtype" in
			fpr)
				if ! output="$($gpg_cmd --with-colons --list-secret-keys "$x_id" 2>/dev/null | 
					awk -F: '$1 == "sec" { hits=1; print $15} END {if (hits==1) exit 0; else exit 1;}')"; then
					echo "Es wurde kein geheimer Schlüssel mit dem Fingerprint"
					echo $'\t'"${x_id}"
					echo "gefunden; Abbruch."
					exit $E_KEYNOTFOUND
				fi
				if [ "#" = "$output" -a yes = "$secmainkeyneeded" ]; then
					echo "Der geheime Schlüssel mit dem Fingerprint"
					echo $'\t'"${x_id}"
					echo "existiert zwar, aber der geheime Hauptschlüssel ist nicht verfügbar; Abbruch."
					exit $E_NOSECMAINKEY
				fi
				found_fingerprint="$hexpart"
				return 0
			;;
		esac
	else
		# COMMENT: Keine ID übergeben: Es darf nur genau einen geheimen Hauptschlüssel geben
		#	Die Auswertung und Fehlerausgabe wird dadurch verkompliziert, dass in manchen 
		#	Fällen der geheime Hauptschlüssel nicht benötigt wird, weswegen die Anzahl 
		#	der geheimen "Unterschlüssel" und die der geheimen Hauptschlüssel getrennt 
		#	betrachtet werden müssen, damit der User nicht durch suboptimale 
		#	Fehlermeldungen verwirrt wird.
		# COMMENT: Anzahl der Schlüssel mit geheimem Hauptschlüssel
		count_main=$($gpg_cmd --with-colons --list-secret-keys | awk -F: 'BEGIN {hits=0};  $1 == "sec" && $15 == "" { hits++;} END {print hits}')
		# COMMENT: Anzahl der Schlüssel ohne geheimen Hauptschlüssel, aber mit geheimem Unterschlüssel
		count_sub=$($gpg_cmd --with-colons --list-secret-keys  | awk -F: 'BEGIN {hits=0}; $1 == "sec" && $15 == "#" { hits++;} END {print hits}')
		if [ yes = "$secmainkeyneeded" ]; then
			count=$count_main
		else
			count=$((count_main+count_sub))
		fi
		if [ 1 -ne "$count" ]; then
			echo "Der Aufruf ohne Schlüssel-ID ist nur zulässig, wenn es genau einen (passenden)"
			echo "geheimen Schlüssel im Schlüsselbund gibt."
			echo
			if [ 0 -eq "$count" ]; then
				if [ 0 -eq $((count_main+count_sub)) ]; then
					echo "Es ist kein geheimer Schlüssel vorhanden; Abbruch."
				else
					echo "Es ist kein geheimer Hauptschlüssel vorhanden; Abbruch."
				fi
			else
				echo "Es ist mehr als ein passender geheimer Schlüssel vorhanden:"
				if [ yes = "$secmainkeyneeded" ]; then
					$gpg_cmd --with-colons --list-secret-keys | 
						awk -F: '$1 == "sec" && $15 == "" { fpr=$5; fprspc=fpr; gsub("....","& ",fprspc); sub(" $","",fprspc); print "\t0x" fpr " (" fprspc ")";}'
				else
					$gpg_cmd --with-colons --list-secret-keys | 
						awk -F: '$1 == "sec" { fpr=$5; fprspc=fpr; gsub("....","& ",fprspc); sub(" $","",fprspc); print "\t0x" fpr " (" fprspc ")";}'
				fi
				echo "Abbruch."
			fi
			exit $E_KEYNOTFOUND
		fi
		# ASSERT: Es gibt genau einen passenden Schlüssel. Fingerprint zur Bestätigung ausgeben
		if [ yes = "$secmainkeyneeded" ]; then
			fingerprint="$($gpg_cmd --with-colons --fingerprint --list-secret-keys | awk -F: '$1 == "sec" && $15=="" { found=1;} $1 == "fpr" && found==1 {print $10; exit}')"
		else
			fingerprint="$($gpg_cmd --with-colons --fingerprint --list-secret-keys | awk -F: '$1 == "sec" { found=1;} $1 == "fpr" && found==1 {print $10; exit}')"
		fi
		if [ 40 -ne "${#fingerprint}" ]; then
			echo "interner Fehler (sub__get_key); Abbruch."
			exit 1
		fi
		short_id=${fingerprint:32}
		long_id=${fingerprint:24}
		clear
		echo "Folgender Schlüssel wurde gefunden:" 
		echo -n $'\t'
		echo ${fingerprint} | sed -n -e 's/..../& /g' -e p
		echo "short ID:         0x${short_id}"
		echo "long  ID: 0x${long_id}"
		echo
		echo "Wenn Sie diesen Schlüssel verwenden möchten, lassen Sie das Script mit"
		echo "Return weiterlaufen; ansonsten brechen Sie es mit ^C (<Strg>-C) ab."
		read cont
		found_fingerprint="$fingerprint"
		return 0
	fi
} # sub__get_key ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__get_passphrase () {
	clear
	if [ yes = "$passphrase_entered" ]; then
		if echo fubar | $gpg_cmd --batch --passphrase "$PASSPHRASE" --no-auto-check-trustdb --armor --local-user ${x_short_id}\! --detach-sign &>/dev/null; then
			return 0
		else
				cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Sie haben zwar in der Umgebungsvariable PASSPHRASE eine Passphrase übergeben, 
aber die ist nicht korrekt.

Sie müssen nun die korrekte Passphrase eingeben.

Weiter mit Return...

EOT
				read cont
		fi
	else
		cat <<EOT
AKTION ERFORDERLICH: Passphrase eingeben
======================================================================

Da die Passphrase nicht in der Umgebungsvariable PASSPHRASE übergeben wurde, 
müssen Sie Sie nun eingeben.

EOT
	fi
	while true; do
		echo
		echo -n "Passphrase (Eingabe wird nicht angezeigt): "
		read -s PASSPHRASE
		if echo fubar | $gpg_cmd --batch --passphrase "$PASSPHRASE" --no-auto-check-trustdb --armor --local-user ${x_short_id}\! --detach-sign &>/dev/null; then
			passphrase_entered=yes
			return 0
		else
			echo "Die eingegebene Passphrase ist nicht korrekt; noch mal versuchen."
		fi
	done
} # sub__get_passphrase ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__prepare_targetdir () {
	local errordir
	{ mkdir -p ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}" ||
		errordir=~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}"; } &&
	{ mkdir -p ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}" ||
		errordir=~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}"; } &&
	{ mkdir -p ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_CERTS}" ||
		errordir=~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_CERTS}"; } &&
	{ mkdir -p ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_MISC}" ||
		errordir=~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_MISC}"; } &&
	{ mkdir -p ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}" ||
		errordir=~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}"; } ||
	{
		clear
		cat <<EOT
AKTION ERFORDERLICH: Dateisystemproblem 
======================================================================

Das Verzeichnis

     '${errordir}'

konnte nicht angelegt werden. Das hätte nicht passieren dürfen.

Bitte untersuchen Sie das Problem und wiederholen den Vorgang.

Nach der Prüfung weiter mit Return...
EOT
		return 1; }
	return 0
} # sub__prepare_targetdir ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__checks_hash () {
	clear
	cat <<EOT
AKTION ERFORDERLICH: Hash notiert?
======================================================================

Hat der zukünftige Schlüsselbesitzer vor der ersten Ausführung den Hash
dieses Scripts ermittelt und ihn auf Papier?

In der Schulung bekommen die Teilnehmer normalerweise vor dem Start der 
Scripte einen Zettel mit zwei Hashwerten, von denen der erste mit
     gpg --print-md SHA1 scripthashes.sh
der zweite mit
     ./scripthashes.sh
erzeugt wird. Darauf bezieht sich diese Sicherheitsnachfrage.

EOT
	echo -n "(j)a / (n)ein:  "
	read answer
	if [ j != "$answer" ]; then
			# COMMENT: This function combines two checks (hash and norandmaps) because the user shall 
			#	reboot in both cases and it would be quite strange to have him reboot twice because 
			#	he reboots after the first check if they were not combined.
			# There is an additional check for norandmaps because the check for the hash storage can 
			#	be skipped.
		if grep -qF norandmaps /proc/cmdline; then
			cat <<EOT
FEHLER (Abbruch)
======================================================================

Bitte das sichere System neu booten und vor dem ersten Start des Scripts 
dessen Hashwert(e) auf Papier notieren. Nur so kann im nachhinein kontrolliert
werden, ob der Schlüssel vertrauenswürdig erzeugt wurde.

Siehe
http://www.openpgp-schulungen.de/scripte/keygeneration/#vertrauen

Das Script beendet sich jetzt.
EOT
			exit $E_PREPARATION
		else
			clear
			cat <<EOT
FEHLER (Abbruch)
======================================================================

Bitte das sichere System neu booten und vor dem ersten Start des Scripts 
dessen Hashwert(e) auf Papier notieren. Nur so kann im nachhinein kontrolliert
werden, ob der Schlüssel vertrauenswürdig erzeugt wurde.
 
Beim Booten nicht wieder den Kernelparameter 'norandmaps' vergessen!

Es ist wenig sinnvoll, dieses Script auf einem sicheren System ohne den 
Kernelparameter 'norandmaps' laufen zu lassen, weil die Programmaufrufe 
dann die kostbare Entropiewegfressen und man viel mehr Tipparbeit hat.

Das Script beendet sich jetzt.
EOT
			exit $E_PREPARATION
		fi
	fi
} # sub__checks_hash ()

###################################################################################################

sub__checks_time () {
	clear
	cat <<EOT
AKTION ERFORDERLICH: Systemzeit OK?
======================================================================

Es kommt häufig zu Problemen zwischen einem sicheren System (Linux von 
CD/DVD) und dem Arbeitssystem im Hinblick auf die Systemzeit. Das führt 
dann im Arbeitssystem beim Import der Schlüssel zu Problemen, weil das 
Arbeitssystem glaubt, die Schlüssel seien in der Zukunft erzeugt worden. 
Das Problem löst sich nach ein paar Stunden, aber ärgerlich ist es dennoch. 

Da es beim Erstellungsdatum des Schlüssels normalerweise nicht auf die 
exakte Zeit ankommt, bietet es sich deshalb an, die Rechnerzeit ein paar 
Stunden zurückzusetzen (wenn das nicht schon passiert ist). Die aktuelle 
Systemzeit ist:
                  $(date)
                  $(date --rfc-3339=seconds)

Weiter mit Return...
EOT
	read cont
} # sub__checks_time ()

###################################################################################################

sub__checks_norandmaps () {
	if ! grep -qF norandmaps /proc/cmdline; then
		clear
		cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

Es ist wenig sinnvoll, dieses Script auf einem sicheren System ohne den 
Kernelparameter 'norandmaps' laufen zu lassen, weil die Programmaufrufe 
dann die kostbare Entropiewegfressen und man mehr Tipparbeit hat.

1) Kernelparameter aufschreiben, abbrechen (^C) und neu booten

oder

2) weiter mit Return...
EOT
		read cont
	fi
} # sub__checks_norandmaps ()

###################################################################################################

sub__checks_screen () {
	if [ "$TERM" != "screen" ]; then
		cat <<EOT
HINWEIS (konkretes Problem)
======================================================================

Es ist im allgemeinen sinnvoll, dieses Script innerhalb von screen auszuführen.

Beenden (mit ^C) oder weiter mit Return...
EOT
		read cont
	fi
} # sub__checks_screen ()

###################################################################################################

sub__checks_cd () {
	# COMMENT: In das Verzeichnis wechseln, in dem dieses Script steht; es muss ja nicht als ./key-generation.sh
	#	aufgerufen werden, sondern kann auch als /pfad/zum/script/key-generation.sh. Dann aber stehen die 
	#	anderen Dateien nicht im aktuellen Verzeichnis.
	test -n "$startpath" &&
	test -d "$startpath" &&
	cd "$startpath" || { echo "Problem beim Wechseln ins Verzeichnis des Scripts"; exit "$E_PREPARATION"; }
} # sub__checks_cd ()

###################################################################################################

sub__checks_gpgconf () {
	if ! [ -f "$CONFIGFILE" ]; then
		echo "Konfigurationsdatei ('${CONFIGFILE}') nicht im aktuellen Verzeichnis gefunden; Abbruch."
		exit $E_NOCONFIGFILE
	fi
} # sub__checks_gpgconf ()

###################################################################################################

sub__checks_keyconf () {
	if ! [ -f "$KEYCONFIGFILE" ]; then
		echo "Schlüsselbeschreibungsdatei ('${KEYCONFIGFILE}') nicht im aktuellen Verzeichnis gefunden; Abbruch."
		exit $E_NOTEMPLATEFILE
	else
		while ! errormessage="$(check_keyconfigfile 2>&1)"; do
			clear
			cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Die Konfigurationsdatei für dieses Script ('${KEYCONFIGFILE}') ist 
fehlerhaft. Dieser Fehler (möglicherweise auch mehrere) muss 
behoben werden, bevor das Script weiterlaufen kann.

Es folgt die Fehlermeldung; allerdings kann es sein, dass der eigentliche 
Fehler weiter oben in der Datei ist als von bash gemeldet:

${errormessage}

Beheben Sie das Problem. Danach weiter mit Return...

EOT
			read cont
		done
		. "$KEYCONFIGFILE"
	fi
} # sub__checks_keyconf ()

###################################################################################################

sub__checks_mvconfdir () {
	if [ -d ~/.gnupg ]; then
		for((index=0;index<100;index++)); do
			if ! [ -d ~/.gnupg.${index} ]; then
				if ! mv --no-target-directory ~/.gnupg ~/.gnupg.${index}; then
					clear
					cat <<EOT
FEHLER (Abbruch)
======================================================================

Das GnuPG-Verzeichnis (~/.gnupg) besteht bereits. Umbenennung in
~/.gnupg.${index} war nicht möglich; Abbruch.
Bitte Löschen / Umbenennen und das Script neu starten.
EOT
					exit $E_GPGCONFRENAME
				else
					if [ yes = "$verbose"  ]; then
						clear
						cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Diese Meldung ist in einer sicheren Umgebung (z.B. Knoppix) irrelevant.

Das GnuPG-Verzeichnis (~/.gnupg) bestand bereits. Es wurde in
~/.gnupg.${index} umbenannt.

Weiter mit Return...
EOT
						read cont
					fi
					break
				fi
			fi
		done
		if [ -d ~/.gnupg ]; then
			clear
			cat <<EOT
FEHLER (Abbruch)
======================================================================

Das GnuPG-Verzeichnis (~/.gnupg) besteht bereits. Umbenennung war 
nicht möglich; Abbruch.
Bitte Löschen / Umbenennen und das Script neu starten.
EOT
			exit $E_GPGCONFRENAME
		fi
	fi # [ -d ~/.gnupg ]
} # sub__checks_mvconfdir ()

###################################################################################################

sub__checks_netwconn () {
	if type netstat &>/dev/null; then
		networkconnections=$(LC_ALL=C netstat --inet --inet6 --numeric | awk -v hits=0 '$4 !~ "^127\\." && $4 !~ "^::1" && $6 == "ESTABLISHED" {hits++}; END {print hits}')
	fi
	if [ -n "$networkconnections" -a 0 -lt "$networkconnections" ]; then
		systemknownunsafe=yes
	fi
} # sub__checks_netwconn ()

###################################################################################################

sub__checks_unsafewarning () {
	if [ yes = "$systemknownunsafe" ]; then
	clear
	cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

Es hat überhaupt keinen Sinn, dieses Script (abgesehen von Testzwecken) in
einem unsicheren System (quasi alles, was nicht von einer DVD verlässlichen
Inhalts, z.B. Knoppix und ähnliche Live-Distributionen) zu verwenden. Wer
keinen wirklich sicheren Schlüssel haben will, kann den einfacher generieren
als mit diesem Script. Auch die Illusion von Sicherheit ist gefährlich!

Dieses System hat offenbar aktive Netzwerkverbindungen zur Außenwelt und kann 
damit in aller Regel nicht mehr als sicher betrachtet werden. Problematisch 
ist alleine schon die MÖGLICHKEIT einer Netzwerkverbindung, also ein aktiver 
Link (Netzwerkkarte mit eingestecktem Kabel; konfiguriertes WLAN). Also im 
Zweifelsfall das System isolieren und neu booten.

Weiter mit Return...
EOT
		read cont
	fi
} # sub__checks_unsafewarning ()

###################################################################################################

sub__checks_showscriptconf () {
	clear
	cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Dieses Script wird - wenn Sie es jetzt nicht abbrechen - versuchen einen neuen 
Schlüssel mit folgenden Eigenschaften zu erzeugen und zu exportieren:
    1) Offline-Hauptschlüssel
	(RSA, ${mainkeylength} Bit; Gültigkeitsdauer: ${expire_period})
    2) einzelne Unterschlüssel für jeweils ausschließlich
	a) Signaturen              (RSA, ${subkeylength} Bit; gültig für: ${expire_period})
	b) Verschlüsselung         (RSA, ${subkeylength} Bit; gültig für: ${expire_period})
    3) die User-IDs aus der Konfigurationsdatei ('${KEYCONFIGFILE}')

Außerdem wird eine angepasste Konfigurationsdatei erzeugt, die normalerweise 
ins Arbeitssystem übernommen werden sollte (etwa dann, wenn dies der erste 
Schlüssel ist oder er in Zukunft als Standardschlüssel genutzt werden soll).

Die Festlegung einer policy URL und eventuell von signature notations in der 
Konfigurationsdatei schon bei der Erzeugung des Schlüssels wird dringend 
empfohlen (siehe die Vorlage für gpg.conf).

Sie können das Script jederzeit z.B. mit ^C abbrechen.
Weiter mit Return...
EOT
	read cont
} # sub__checks_showscriptconf ()

###################################################################################################

sub__checks_initconfdir () {
	# COMMENT: ~/.gnupg initialisieren
	$gpg_cmd --list-keys &>/dev/null

	if ! [ -d ~/.gnupg ]; then
		echo "Das GnuPG-Verzeichnis existiert nicht, obwohl es gerade hätte angelegt werden müssen; Abbruch."
		exit $E_GPGCONFNOTEXISTS
	fi

	cp "$CONFIGFILE" "$KEYCONFIGFILE" ~/.gnupg/
	if [ -f "$AGENT_CONFIGFILE" ]; then
		cp "$AGENT_CONFIGFILE" ~/.gnupg/
	fi

	# COMMENT: Providing a (secure! i.e. new) random_seed file saves a lot of entropy
	#	The time for the preparations for running this script can be used to create 
	#	this file (and nonetheless fill up the entropy pool afterwards before this 
	#	script needs it).
	if [ -n "$seedfile" ]; then
		cp "$seedfile" ~/.gnupg/random_seed
		chmod 600 ~/.gnupg/random_seed
		wipe_file "$seedfile"
	fi
} # sub__checks_initconfdir ()

###################################################################################################

sub__checks_targetdir () {
	local deleted_link="no" found_target_path
	if [ -d "${startpath}/${DEFAULT_TARGET_SUBDIR}" ]; then
		read_init="${startpath}/${DEFAULT_TARGET_SUBDIR}"
	elif [ -d "${startpath}/../${DEFAULT_TARGET_SUBDIR}" ]; then
		read_init="$(readlink --no-newline -f "${startpath}/../${DEFAULT_TARGET_SUBDIR}"; echo x)"
		read_init="${read_init%x}"
	else
		read_init="${startpath}"
	fi
	while true; do
		if ! [ no = "$verbose" -a \( \( -d "${startpath}/${DEFAULT_TARGET_SUBDIR}" -a -w "${startpath}/${DEFAULT_TARGET_SUBDIR}" \) \
			-o \( -d "${startpath}/../${DEFAULT_TARGET_SUBDIR}" -a -w "${startpath}/../${DEFAULT_TARGET_SUBDIR}" \) \) ]; then
			: if cond is true
			clear
			while [ -e ~/.gnupg/"$COPYFILESTODIR" ]; do
				if [ "no" = "$deleted_link" -a -L ~/.gnupg/"$COPYFILESTODIR" ]; then
					rm ~/.gnupg/"$COPYFILESTODIR"
					deleted_link="yes"
				else
					clear
					cat <<EOT
AKTION ERFORDERLICH: Symlink '${COPYFILESTODIR}' existiert schon
======================================================================

Es besteht bereits ein Dateisystemobjekt

              ~/.gnupg/"$COPYFILESTODIR"

Allerdings handelt es sich dabei entweder nicht um einen Symlink, oder der 
Symlink konnte nicht gelöscht werden. Beheben Sie das Problem manuell.

Anschließend weiter mit Return...
EOT
					read cont
				fi
			done
			deleted_link="no"
			clear
			cat <<EOT
AKTION ERFORDERLICH: Symlink '${COPYFILESTODIR}' anlegen (Info)
======================================================================

Jetzt muss festgelegt werden, in welches Verzeichnis die später erzeugten 
Dateien geschrieben werden.
Typischerweise ist das das Verzeichnis '${DEFAULT_TARGET_SUBDIR}' in dem 
Verzeichnis, aus dem dieses Script gestartet wurde. In dem Fall brauchen 
Sie nur zweimal Return zu drücken.

Technisch erfolgt die Festlegung des Ziels in ~/.gnupg/ durch den Symlink 
'${COPYFILESTODIR}' auf das Zielverzeichnis. Sie können das selber machen 
(in einer anderen Shell oder eventuell mit der grafischen Oberfläche). 
Am einfachsten dürfte aber zumeist sein, Sie drücken gleich Return und 
erledigen dies mit der Funktion dieses Scripts (enthält 
Pfadvervollständigung).

Mounten Sie jetzt bitte das Volume mit dem Zielverzeichnis, falls das noch 
nicht geschehen ist, und machen dann...

...weiter mit Return...
EOT
			read cont
			cat <<EOT
======================================================================

Es folgt eine Liste der aktuell gemounteten Volumes:

EOT
			max_mp_path_length="$(awk '! ($3 == "devtmpfs" || $3 == "tmpfs" || $3 == "proc" || $3 == "sysfs" || 
			  $3 == "securityfs" || $3 == "cgroup" || $3 == "autofs" || $3 == "mqueue" || $3 == "hugetlbfs" || 
			  $3 == "debugfs" || $3 == "rootfs" || $3 == "devpts" || $3 ~ /fuse/) {if (length($2) > max_mp_path_length) max_mp_path_length=length($2);}
			  END {print max_mp_path_length}'  /proc/mounts)"
			awk -v max_mp_path_length="$max_mp_path_length" '! ($3 == "devtmpfs" || $3 == "tmpfs" || $3 == "proc" || $3 == "sysfs" || 
			  $3 == "securityfs" || $3 == "cgroup" || $3 == "autofs" || $3 == "mqueue" || $3 == "hugetlbfs" || 
			  $3 == "debugfs" || $3 == "rootfs" || $3 == "devpts" || $3 ~ /fuse/) {printf "%-" max_mp_path_length "s : %s (%s)\n",$2,$1,$3;}'  /proc/mounts
			pwd_length="${#PWD}"
			pwd_offset=$((pwd_length+(79-pwd_length)/2))
			cat <<EOT

AKTION ERFORDERLICH: Symlink '${COPYFILESTODIR}' anlegen
======================================================================

Wenn Sie jetzt (zweimal) <Tab> drücken, müsste die Shell Ihnen die jeweils 
möglichen Pfade anbieten. Die Angabe "/" ist ungültig und wird ignoriert. 
Auch die Angabe relativer Pfade ist möglich; das aktuelle Verzeichnis ist:

$(printf "%${pwd_offset}s" "$PWD")

Wenn Sie den Link '${COPYFILESTODIR}' in ~/.gnupg/ selber anlegen wollen (in 
einer anderen Shell), machen Sie das und drücken anschließend hier Return.

EOT
			read -e -i "$read_init" targetpath
			read_init="$targetpath"
			# ASSERT: If ~/.gnupg/"$COPYFILESTODIR" exists then is has been created after "read ... targetpath"
			# BEGIN: Ignore the input if the symlink has been created outside this script
			if [ -L ~/.gnupg/"$COPYFILESTODIR" -a -d ~/.gnupg/"$COPYFILESTODIR" ]; then
				if ! [ -w ~/.gnupg/"$COPYFILESTODIR" ]; then
					echo
					echo "Das Zielverzeichnis ist nicht beschreibbar. Neuer Versuch..."
					echo "Weiter mit Return..."
					read cont
					continue
				fi
				if [ -n "$(echo ~/.gnupg/"$COPYFILESTODIR"/*)" ]; then
					echo
					if yesno "Das Zielverzeichnis ist nicht leer. Trotzdem verwenden?"; then
						sub__prepare_targetdir && break || continue
					else
						continue
					fi
				else
					sub__prepare_targetdir && break || continue
				fi
			# END:   Ignore the input if the symlink has been created outside this script
			else
                                if [ "" = "$targetpath" -o "/" = "$targetpath" ]; then
                                        echo "Illegale Eingabe!"
                                        echo "Weiter mit Return..."
                                        read cont
                                        continue
                                fi
                                if [ -d "$targetpath" ]; then
                                        targetpath="$(readlink -f "$targetpath")"
                                        (
                                                # COMMENT: Use a subshell so that "cd" does not affect the script
                                                set -x
                                                cd ~/.gnupg
                                                ln -s --no-target-directory "$targetpath" "$COPYFILESTODIR"
                                        ); test $? -gt 0 && exit 1
                                else
                                        echo "'${targetpath}' ist kein Verzeichnis!"
                                        echo "Weiter mit Return..."
                                        read cont
                                        continue
                                fi
                                if [ -L ~/.gnupg/"$COPYFILESTODIR" -a -d ~/.gnupg/"$COPYFILESTODIR" ]; then
                                        if ! [ -w ~/.gnupg/"$COPYFILESTODIR" ]; then
                                                echo
                                                echo "Das Zielverzeichnis ist nicht beschreibbar. Neuer Versuch..."
                                                echo "Weiter mit Return..."
                                                read cont
                                                continue
                                        fi
                                fi
                                if [ -n "$(echo ~/.gnupg/"$COPYFILESTODIR"/*)" ]; then
                                        echo
                                        if yesno "Das Zielverzeichnis ist nicht leer. Trotzdem verwenden?"; then
                                                sub__prepare_targetdir && break || continue
                                        else
                                                continue
                                        fi
                                else
                                        sub__prepare_targetdir && break || continue
                                fi
			fi
		else
			# COMMENT: non-interactive block
			# ${startpath}/../${DEFAULT_TARGET_SUBDIR}" is the priority path
			if [ -d "${startpath}/../${DEFAULT_TARGET_SUBDIR}" ]; then
				found_target_path="$(readlink --no-newline -f "${startpath}/../${DEFAULT_TARGET_SUBDIR}"; echo x)"
				found_target_path="${found_target_path%x}"
			else
				found_target_path="${startpath}/${DEFAULT_TARGET_SUBDIR}"
			fi
			if [ -n "$(echo "${found_target_path}"/*)" ]; then
				echo
				echo "Das Zielverzeichnis"
				echo "       '${found_target_path}'"
				echo "ist nicht leer. Ggf. korrigieren und dann weiter mit Return..."
				read cont
				continue
			fi
			targetpath="$found_target_path"
			(
				# COMMENT: Use a subshell so that "cd" does not affect the script
				cd ~/.gnupg
				ln -s --no-target-directory "$targetpath" "$COPYFILESTODIR"
			); test $? -gt 0 && exit 1
			sub__prepare_targetdir && break || continue
		fi
	done
} # sub__checks_targetdir ()

###################################################################################################

sub__checks_configfileok () {
	local configfile_grep_turn=0 # zur Anpassung der Meldung
	while { set_startline_number; cat_config_for_modify | grep -q -- "$MODIFY"; }; do
		clear
		if [ $configfile_grep_turn -gt 0 ]; then
			echo "Das war der ${configfile_grep_turn}. Fehlversuch."
			echo "Es befindet sich noch mindestens ein '${MODIFY}' in der Datei,"
			echo "und zwar in folgenden Zeilen:"
			: DEBUG: startline_number = $startline_number
			cat_config_for_modify | 
				awk -v lineoffset="$startline_number" -v searchstring="$MODIFY" \
				'$0 ~ searchstring {line=NR+lineoffset; print "Zeile " line ": " $0;}'
			echo
		fi
		if [ "$configfile_grep_turn" -gt 0 ]; then
			echo "Jetzt bitte die Konfigurationsdatei ('${CONFIGFILE}') bearbeiten."
		else
			cat <<EOT
AKTION ERFORDERLICH: Konfigurationsdatei bearbeiten
======================================================================

Jetzt bitte die Konfigurationsdatei ('${CONFIGFILE}') bearbeiten.

EOT
		fi
		if [ "$startline_number" -eq 0 ]; then
			echo "Zumindest alle Zeilen, in denen '${MODIFY}' steht."
			echo "Dieses Schlüsselwort anschließend löschen."
		else
			echo "Zumindest alle Zeilen (unterhalb der Zeile '${MODIFICATIONSTARTLINE}'),"
			echo "in denen '${MODIFY}' steht. Dieses Schlüsselwort anschließend löschen."
		fi
		echo
		echo "Weiter mit Return..."
		read cont
		((configfile_grep_turn++)) || true
	done
} # sub__checks_configfileok ()

###################################################################################################

sub__checks_uid0name () {
	if [ -z "$uid_0_name" ]; then
		echo "Die erste User-ID hat keinen Namen; Abbruch."
		exit $E_UIDERROR
	fi
} # sub__checks_uid0name ()

###################################################################################################

sub__checks_agent () {
	if ! type gpg-agent &>/dev/null; then
	clear
	cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

Auf diesem System ist gpg-agent nicht verfügbar. Dieses Script kann auch 
ohne gpg-agent laufen, dann müssen aber in erheblichem Umfang Passwörter 
eingegeben werden, was lästig (und fehleranfällig) ist und die Ausführung 
erheblich verzögert.

Wenn Sie also die Möglichkeit haben, gpg-agent zu installieren, ohne die 
Integrität des Systems zu gefährden (also eher nicht übers Netzwerk), dann 
sollten Sie das jetzt tun und anschließend Return drücken.

Weiter mit Return...
EOT
		read cont
		
	fi
} # sub__checks_agent ()

###################################################################################################

sub__checks_copyscript () {
	cp "${startpath}/$0" ~/.gnupg/"$SCRIPTCOPYNAME"
} # sub__checks_copyscript ()

###################################################################################################

#sub__checks_ () {
#} # sub__checks_ ()

###################################################################################################

sub__checks () {
	local index targetpath errormessage max_mp_path_length pwd_length pwd_offset
	local run_var run_vars run_function
	clear
	run_vars=(run__sub__checks_hash \
		run__sub__checks_time \
		run__sub__checks_norandmaps \
		run__sub__checks_screen \
		run__sub__checks_cd \
		run__sub__checks_gpgconf \
		run__sub__checks_keyconf \
		run__sub__checks_mvconfdir \
		run__sub__checks_initconfdir)
	for run_var in "${run_vars[@]}"; do
		if [ -n "${!run_var}" ]; then
			run_function="${run_var#run__}"
			"$run_function"
		fi
	done
	cd ~/.gnupg
	run_vars=(run__sub__checks_netwconn \
		run__sub__checks_unsafewarning \
		run__sub__checks_showscriptconf \
		run__sub__checks_copyscript \
		run__sub__checks_targetdir \
		run__sub__checks_configfileok \
		run__sub__checks_uid0name) # run__sub__checks_agent)
	for run_var in "${run_vars[@]}"; do
		if [ -n "${!run_var}" ]; then
			run_function="${run_var#run__}"
			"$run_function"
		fi
	done
} # sub__checks ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__mainkeygen () {
	if [ yes = "$verbose" ]; then
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Wenn Sie jetzt Return drücken, beginnt die Schlüsselgenerierung. Das Script 
sollte danach nicht mehr abgebrochen werden.
EOT
		read cont
	fi

	cat <<-EOF >"$KEYGENERATIONFILE"
		Key-Type: RSA
		Key-Length: ${mainkeylength}
		Key-Usage: encrypt,sign
		Name-Real: ${uid_0_name}
		Expire-Date: $expire_period
		Passphrase: $GENERATIONPASSPHRASE
	EOF

	if [ -n "$uid_0_comment" ]; then
		echo "Name-Comment: ${uid_0_comment}" >>"$KEYGENERATIONFILE"
	fi

	if [ -n "$uid_0_email" ]; then
		echo "Name-Email: ${uid_0_email}" >>"$KEYGENERATIONFILE"
	fi
	echo "%commit" >>"$KEYGENERATIONFILE"
	screenswitch_collect
	if ! $gpg_cmd --batch --status-fd 3 --gen-key "$KEYGENERATIONFILE" 2>/dev/null 3>"$LOGFILE" ; then
		echo "Fehler bei der Erzeugung des Hauptschlüssels; Abbruch."
		exit $E_MAINKEYGEN
	fi
	fingerprint=$(awk '$2 == "KEY_CREATED" {print $4}' "$LOGFILE")
	if [ "${#fingerprint}" -ne 40 ]; then
		echo "Fingerprint des erzeugten Schlüssels kann nicht bestimmt werden; Abbruch."
		exit $E_FPR
	fi
	$gpg_cmd --armor --export "0x${fingerprint}" >"$KEYFILE_PUBLIC_TMP"
	if [ "yes" = "$gpgagent_running" ]; then
		echo -n "$GENERATIONPASSPHRASE" | gpg-preset-passphrase --preset "$fingerprint"
	fi
	clear
	cat <<EOT
PROGRAMMERGEBNIS (sollte man beachten)
======================================================================

Der Fingerprint des Schlüssels ist:
EOT
	echo "     $(echo ${fingerprint} | sed -n -e 's/..../& /g' -e p)"
	echo "Diesen Wert bitte notieren und sicher verwahren."
	echo "    (Ggf. ein zweites Mal für den Dozenten.)"
	echo "Die letzten 8/16 Zeichen sind die short/long ID des Schlüssels:"
	echo "     short ID:         0x${fingerprint:32}"
	echo "     long ID:  0x${fingerprint:24}"
	echo
	trap trap_sigint INT
	echo "Hinweis: Ab jetzt kann das Script nicht mehr mit Strg-C abgebrochen werden."
	echo "Das ist eine Vorsichtsmaßnahme. Man kann es immer noch irregulär beenden:"
	echo "1) Strg-Z und dann kill %1"
	echo "2) in einer anderen Konsole: kill $$"
	echo
	while true; do
		if ! yesno "Haben Sie den Fingerprint abgeschrieben?"; then
			echo
			echo -n "Dann machen Sie das mal"
			sleep 1; echo -n .
			sleep 1; echo -n .
			sleep 1; echo .
			continue
		else
			return 0
		fi
	done
} # sub__mainkeygen ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__set_vars () {
	if [ "${#fingerprint}" -ne 40 ]; then
		echo "Fingerprint kann nicht bestimmt werden; Abbruch."
		exit $E_FPR
	fi
	x_fingerprint=0x${fingerprint}

	short_id=${fingerprint:32}
	long_id=${fingerprint:24}
	x_short_id=0x${short_id}
	x_long_id=0x${long_id}

	keyfile_secret_mainkey=${x_short_id}.secret-mainkey.asc
	keyfile_secret_mainkey_only=${x_short_id}.secret-mainkey-only.asc
	keyfile_secret_subkeys=${x_short_id}.secret-subkeys.asc
	keyfile_public=${x_short_id}.public.asc
	keyfile_public_mainkeyonly=${x_short_id}.public-mainkeyonly.asc
} # sub__set_vars ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__adapt_config () {
	clear
	local startlinefound=no file_before_mod file_after_mod
	# COMMENT: Nur den Teil der Datei bearbeiten, der unterhalb der Zeile $MODIFICATIONSTARTLINE ist
	echo -n > "$CONFIGFILE_TMP1"
	echo -n > "$CONFIGFILE_TMP2"
	exec {TMPFD}>>"$CONFIGFILE_TMP1"
	while read line; do
		if [[ $line = $MODIFICATIONSTARTLINE ]]; then
			exec {TMPFD}>&-
			exec {TMPFD}>>"$CONFIGFILE_TMP2"
			startlinefound=yes
		fi
		echo "$line" >&$TMPFD
	done < "${CONFIGFILE}"
	exec {TMPFD}>&-
	if [ no = "$startlinefound" ]; then
		cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

In der GnuPG-Konfigurationsdatei ('${CONFIGFILE}') wurde die Zeile nicht
gefunden, die die Schlüsselwörter aktiviert. Ohne diese Zeile wird die
gesamte Datei bearbeitet. Das muss kein Fehler sein! Problematisch ist es
dann, wenn die Schlüsselwörter in Kommentartexten auftauchen, wo sie von
diesem Script natürlich nicht bearbeitet werden sollen. Schauen Sie also
ggf. in die Datei und beheben, wenn nötig, das Problem, indem Sie entweder
entsprechende Kommentartexte (bzw. die Schlüsselwörter dort) löschen (oder
"entschärfen") oder die Aktivierungszeile an geeigneter Stelle einfügen.
Diese Zeile sieht folgendermaßen aus:

$MODIFICATIONSTARTLINE

Weiter mit Return...
EOT
		read cont
		cp -p "${CONFIGFILE}" "${CONFIGFILE}".bak
		file_before_mod="${CONFIGFILE}".bak
		file_after_mod="${CONFIGFILE}"
		if ! sed -i -e s/__SHORTID__/${x_short_id}/ -e s/__LONGID__/${x_long_id}/ -e s/__FINGERPRINT__/${x_fingerprint}/ -e 's/^.*_REMOVE_//' "${file_after_mod}" ; then
			echo "Fehler beim Anpassen der GnuPG-Konfigurationsdatei ('${CONFIGFILE}'); Abbruch."
			exit $E_MODIFYGPGCONFIG
		else
			if cmp_files "${file_after_mod}" "${file_before_mod}"; then
				clear
				cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

Die Konfigurationsdatei wurde nicht verändert. Das ist nicht in jedem 
Fall ein Problem! Genau das kann beabsichtigt gewesen sein. Wenn das 
allerdings nicht beabsichtigt war, müssen Sie die Datei jetzt (in einer 
anderen Konsole) in der gewünschten Weise anpassen (oder das Problem 
in der Vorlage beheben und dieses Script erneut starten). Als Hilfe 
dafür noch mal die Schlüsseldaten (zum Kopieren per Maus oder screen):

Fingerprint: 0x${fingerprint}

short ID:    0x${fingerprint:32}"

long ID:     0x${fingerprint:24}"


Weiter mit Return...
EOT
				read cont
			
			fi
			if [ yes = "$verbose" ]; then
				clear
				cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Die GnuPG-Konfigurationsdatei ('${CONFIGFILE}') wurde überarbeitet.
Bitte prüfen Sie, ob das Ergebnis Ihren Wünschen entspricht (und passen die Datei ggf. an).

EOT
                                if type diff &>/dev/null; then
                                        echo "Dies sind die veränderten Zeilen:"
                                        echo "########################################"
                                        diff --old-line-format= --unchanged-line-format= --new-line-format=%L$'\n' "$file_before_mod" "$file_after_mod" || true
                                        echo "########################################"
                                        echo
                                fi
                                echo "Weiter mit Return..."
                                read cont
			fi
		fi
	else
		cp -p "${CONFIGFILE_TMP2}" "${CONFIGFILE_TMP2}".bak
		file_before_mod="${CONFIGFILE_TMP2}".bak
		file_after_mod="${CONFIGFILE_TMP2}"
		if ! sed -i -e s/__SHORTID__/${x_short_id}/ -e s/__LONGID__/${x_long_id}/ -e s/__FINGERPRINT__/${x_fingerprint}/ -e 's/^.*_REMOVE_//' "${file_after_mod}" ; then
			echo "Fehler beim Anpassen der GnuPG-Konfigurationsdatei ('${CONFIGFILE}'/'${file_after_mod}'); Abbruch."
			exit $E_MODIFYGPGCONFIG
		else
			if cmp_files "${file_after_mod}" "${file_before_mod}"; then
				clear
				cat <<EOT
WARNUNG (konkretes Problem)
======================================================================

Die Konfigurationsdatei wurde nicht verändert. Das ist nicht in jedem 
Fall ein Problem! Genau das kann beabsichtigt gewesen sein. Wenn das 
allerdings nicht beabsichtigt war, müssen Sie die Datei jetzt (in einer 
anderen Konsole) in der gewünschten Weise anpassen (oder das Problem 
in der Vorlage beheben und dieses Script erneut starten). Als Hilfe 
dafür noch mal die Schlüsseldaten (zum Kopieren per Maus oder screen):

Fingerprint: 0x${fingerprint}

short ID:    0x${fingerprint:32}"

long ID:     0x${fingerprint:24}"


Weiter mit Return...
EOT
				read cont
			
			fi
			cat "$CONFIGFILE_TMP1" "$CONFIGFILE_TMP2" > "${CONFIGFILE}"
			if [ yes = "$verbose" ]; then
				clear
				cat <<EOT
PROGRAMMERGEBNIS (sollte man beachten)
======================================================================

Die GnuPG-Konfigurationsdatei ('${CONFIGFILE}') wurde überarbeitet.
Bitte prüfen Sie, ob das Ergebnis Ihren Wünschen entspricht (und passen die Datei ggf. an).

EOT
                                if type diff &>/dev/null; then
                                        echo "Dies sind die veränderten Zeilen:"
                                        echo "########################################"
                                        diff --old-line-format= --unchanged-line-format= --new-line-format=%L$'\n' "$file_before_mod" "$file_after_mod" || true
                                        echo "########################################"
                                        echo
                                fi
                                echo "Weiter mit Return..."
                                read cont
			fi # if [ yes = "$verbose" ]; then
		fi
	fi
	# COMMENT: Prüfen, ob die Syntax der Konfigurationsdatei korrekt ist und nicht irgendwelcher Scheiß 
	#	passiert ist, der gpg beim nächsten Aufruf abschmieren lässt...
	while ! gpg --list-keys &>/dev/null; do
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Die GnuPG-Konfigurationsdatei ('${CONFIGFILE}') ist fehlerhaft; gpg kann so 
nicht ausgeführt werden. Dieser Fehler (möglicherweise auch mehrere) muss 
behoben werden, bevor das Script weiterlaufen kann.

gpg wird jetzt gestartet, damit es hoffentlich eine Fehlermeldung produziert,
die Ihnen dabei hilft, das Problem zu finden.


EOT
	gpg --list-keys
	echo
	echo "Nach der Fehlerbehebung weiter mit Return..."
	read cont
	done
} # sub__adapt_config ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__uids_subkeys () {
	local varname_name varname_email varname_comment index name email comment output file
	echo "INFO: Eigensignatur (ohne policy URL) löschen und neue erzeugen"
	echo "uid 1"$'\n'delsig$'\n'y$'\n'y$'\n'sign$'\n'y$'\n'save$'\n' | LC_ALL= LANGUAGE=en $gpg_cmd --batch --display-charset utf-8 --utf8-strings "${SIGADVERTISINGUID[@]}" --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key $x_short_id &>/dev/null

	echo "INFO: Unterschlüssel für Signaturen erzeugen"
	screenswitch_collect
	echo addkey$'\n'8$'\n'e$'\n'q$'\n'${subkeylength}$'\n'"$expire_period"$'\n'save$'\n' | LC_ALL= LANGUAGE=en $gpg_cmd --batch --display-charset utf-8 --utf8-strings "${SIGADVERTISINGSUBKEY[@]}" --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key $x_short_id # &>/dev/null
	if [ yes != "$DEBUG" ]; then
		echo "INFO: Unterschlüssel für Verschlüsselung erzeugen"
		echo addkey$'\n'8$'\n's$'\n'q$'\n'${subkeylength}$'\n'"$expire_period"$'\n'save$'\n' | LC_ALL= LANGUAGE=en $gpg_cmd --batch --display-charset utf-8 --utf8-strings "${SIGADVERTISINGSUBKEY[@]}" --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key $x_short_id # &>/dev/null
		# COMMENT: Seit 1.8 (05.08.2013) nicht mehr standardmäßig einen SSH-Schlüssel erzeugen; kostet Zeit, braucht quasi keiner; kann man nachrüsten
		# echo "INFO: Unterschlüssel für Authentifizierung (SSH) erzeugen"
		# echo addkey$'\n'8$'\n's$'\n'e$'\n'a$'\n'q$'\n'${subkeylength}$'\n'"$expire_period"$'\n'save$'\n' | LC_ALL= LANGUAGE=en $gpg_cmd --batch --display-charset utf-8 --utf8-strings "${SIGADVERTISINGSUBKEY[@]}" --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key $x_short_id # &>/dev/null
	else
		echo "Debug-Modus: Unterschlüssel für Verschlüsselung und Authentifizierung"
		echo "werden NICHT erzeugt."
	fi
	echo
	echo "INFO: temporärer Export des privaten Hauptschlüssels"
	$gpg_cmd --check-trustdb &>/dev/null
	$gpg_cmd --no-auto-check-trustdb --export-secret-keys "${x_short_id}" > "$KEYFILE_SECRET_MAINKEY_TMP"
	sleep 1
	echo
	# BEGIN: UIDs erzeugen
	index=1
	existing_uids=("${uid_0_name}${uid_0_email}${uid_0_comment}") # prevent duplicates
	while true; do
		varname_name=uid_${index}_name
		varname_email=uid_${index}_email
		varname_comment=uid_${index}_comment
		name="${!varname_name}"
		email="${!varname_email}"
		comment="${!varname_comment}"
		if [ -n "$name" -o -n "$email" -o "$comment" ]; then
			new_uid="${name}${email}${comment}"
			if ! unique_uid "$new_uid"; then
				echo "FEHLER: Die User-ID"
				echo $'\t'"Name: '${name}'"
				echo $'\t'"E-Mail: '${email}'"
				echo $'\t'"Kommentar: '${comment}'"
				echo "ist doppelt."
				echo "Weiter mit Return..."
				read cont
			else
				existing_uids[index]="$new_uid"
				# COMMENT: Ensure that the signature timestamps differ
				sleep 1
				if ! echo adduid$'\n'"$name"$'\n'"${email}"$'\n'"${comment}"$'\n'save$'\n' | LC_ALL= LANGUAGE=en $gpg_cmd --batch --display-charset utf-8 --utf8-strings "${SIGADVERTISINGUID[@]}" --allow-freeform-uid --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key $x_short_id &>/dev/null; then
					echo "Beim Erzeugen der User-ID"
					echo $'\t'"Name: '${name}'"
					echo $'\t'"E-Mail: '${email}'"
					echo $'\t'"Kommentar: '${comment}'"
					echo "ist leider ein Fehler aufgetreten. Bitte benachrichtigen Sie die Entwickler des Scripts."
					echo "Weiter mit Return..."
					read cont
				fi
			fi
		else
			break
		fi
		((index++)) || true
	done
	# END:  UIDs erzeugen
	clear
	if [ yes = "$verbose" ]; then
		$gpg_cmd --check-trustdb &>/dev/null
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

So sieht der fertige Schlüssel aus:
(Ausgabe von $gpg_cmd --list-sigs $x_short_id)

EOT
		$gpg_cmd --no-auto-check-trustdb --list-sigs $x_short_id
		echo
		echo "Weiter mit Return..."
		read cont
	fi # if [ yes = "$verbose" ]; then
	output="$(gpg --no-auto-check-trustdb --fixed-list-mode --with-colons --list-keys "$x_short_id" | awk -F: '$1 == "sub" && $12 !~ "^[sea]$"')"
	if [ yes = "$verbose" ]; then
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

So sieht der fertige Schlüssel aus - in einer anderen Ansicht, damit man 
die Schlüsselfähigkeiten sieht 
(Ausgabe von $gpg_cmd --edit-key $x_short_id quit):

EOT
	fi # if [ yes = "$verbose" ]; then
	if [ -n "$output" ]; then
		cat <<EOT

WARNUNG (konkretes Problem)
======================================================================

Mindestens ein Unterschlüssel hat mehr als ein Flag ("S", "E", "A").
Das ist wahrscheinlich ein Fehler.

EOT
	fi
	$gpg_cmd --check-trustdb &>/dev/null
	$gpg_cmd --no-auto-check-trustdb --edit-key $x_short_id quit 2>/dev/null
	echo
	echo "Weiter mit Return..."
	read cont
} # sub__uids_subkeys ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__primary_uid () {
	local uid_count key_ok=no uid_index i
	uid_count="$($gpg_cmd --fixed-list-mode --with-colons --list-keys $x_short_id | grep -c ^uid)"
	if [ "$uid_count" -gt 1 -a \! \( "$uid_count" -eq 2 -a "yes" = "$INFO_UID" -a "no" = "$verbose" \) ]; then
		# COMMENT: Save some time by marking the most probably wanted UID as primary
		#	This is the last UID if it contains an email address but the one before if it doesn't
		#	Time is saved because in most cases this block can be skipped.
		#	The UIDs are listed in the reverse order of creation due to the sorting 
		#	by signature timestamp.
		if [ -n "$uid_0_email" ]; then
			echo "uid ${uid_count}"$'\n'primary$'\n'save$'\n' | LC_MESSAGES=C $gpg_cmd --batch --utf8-strings "${SIGADVERTISINGUID[@]}" --command-fd 0 --allow-freeform-uid --passphrase "$GENERATIONPASSPHRASE" --no-auto-check-trustdb --edit-key "$x_short_id" &>/dev/null
		else
			echo "uid $((uid_count-1))"$'\n'primary$'\n'save$'\n' | LC_MESSAGES=C $gpg_cmd --batch --utf8-strings "${SIGADVERTISINGUID[@]}" --command-fd 0 --allow-freeform-uid --passphrase "$GENERATIONPASSPHRASE" --no-auto-check-trustdb --edit-key "$x_short_id" &>/dev/null
		fi
		while [ no = "$key_ok" ]; do
			clear
			cat <<EOT
AKTION ERFORDERLICH: Festlegung der primären User-ID
======================================================================

Der erzeugte Schlüssel hat mehr als eine User-ID. Das ist kein Problem, 
allerdings gibt es viele Situationen, in denen nur eine User-ID angezeigt 
wird. Man kann festlegen, welche das sein soll (die "primäre"), damit nicht 
"zufällig" eine ausgewählt wird. 

EOT
			i=1; $gpg_cmd --fixed-list-mode --with-colons --list-keys "$x_short_id" | 
				while IFS=: read -r type d2 d3 d4 d5 d6 d7 d8 d9 text; do
					if [ "uid" = "$type" ]; then
						text="${text//\\x3a/:}"
						echo "${i}: ${text}"
						((i++))
					fi
				done
			cat <<EOT

Geben Sie die Nummer derjenigen User-ID ein, um die primäre festzulegen, 
wenn die gewünschte User-ID nicht jetzt schon an erster Stelle steht, 
oder "0", um diesen Schritt zu überspringen.

EOT
			if [ -z "$uid_0_email" -a "$uid_count" -eq 2 ]; then
				# ASSERT: only one UID with email address
				cat <<EOT

Wahrscheinlich möchten Sie hier 0 eingeben, um die aktuelle Einstellung 
beizubehalten.


EOT
				read -p "Index der primären User-ID (oder 0 zum Überspringen): " -e -i 0 uid_index
			else
				read -p "Index der primären User-ID (oder 0 zum Überspringen): " -e uid_index
			fi
			if [[ ! ( "$uid_index" =~ ^(0|[1-9]|[1-9][0-9]*)$ ) || "$uid_index" -gt "$uid_count" ]]; then
				clear
				cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Die Eingabe ('${uid_index}') ist ungültig.

Weiter mit Return...

EOT
				read cont
				continue
			fi
			if [ 0 -eq "$uid_index" ]; then
				break
			fi
			echo "uid ${uid_index}"$'\n'primary$'\n'save$'\n' | LC_MESSAGES=C $gpg_cmd --batch --utf8-strings "${SIGADVERTISINGUID[@]}" --command-fd 0 --allow-freeform-uid --passphrase "$GENERATIONPASSPHRASE" --no-auto-check-trustdb --edit-key "$x_short_id"
			clear
			cat <<EOT

AKTION ERFORDERLICH: Ergebnis OK?
======================================================================

Dies ist jetzt die erste User-ID:

EOT
			$gpg_cmd --list-options no-show-uid-validity --no-auto-check-trustdb --list-keys "$x_short_id" | awk '$1 == "uid" { $1=""; print; exit; }'
			echo
			echo
			if yesno "Ist das so OK?"; then
				key_ok=yes
			fi
		done
	fi
} # sub__primary_uid ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__manual_changes () {
	clear
	cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Jetzt können Sie (in einer anderen Konsole) Änderungen am Schlüssel vornehmen,
die das Script (noch) nicht kann, etwa
	- die Festlegung eines designated revoker
oder, ähem, Fehler im Schlüssel korrigieren.

EOT
	echo "Weiter mit Return..."
	read cont
} # sub__manual_changes ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__lckeygen () {
	clear
	cat <<EOT
AKTION ERFORDERLICH: lokalen Zertifizierungsschlüssel erzeugen?
======================================================================

In den meisten Fällen ist es sinnvoll, einen lokalen Zertifizierungsschlüssel 
zu erzeugen. Schaden kann es nicht (man muss ihn nicht verwenden, wenn man 
schon einen geeigneten hat).

EOT
	if ! yesno "Soll auch ein lokaler Zertifizierungsschlüssel erzeugt werden?"; then
		return 0
	fi

	cat <<-EOF >"$LCKEYGENERATIONFILE"
Key-Type: RSA
Key-Usage: auth
Key-Length: 1024
Name-Real: lckey-lsign
Name-Comment: nur für lokale Zertifizierungen
Expire-Date: 0
Passphrase: $GENERATIONPASSPHRASE
%commit
	EOF

	screenswitch_collect
	if ! $gpg_cmd --batch --status-fd 3 --gen-key "$LCKEYGENERATIONFILE" 3>"$LCKEYLOGFILE" ; then
		echo "Fehler bei der Erzeugung des lokalen Zertifizierungsschlüssels; Abbruch."
		return $E_LCKEYGEN
	fi
	lckeyfingerprint=$(awk '$2 == "KEY_CREATED" {print $4}' "$LCKEYLOGFILE")
	if [ "${#lckeyfingerprint}" -ne 40 ]; then
		echo "Fingerprint des erzeugten Schlüssels kann nicht bestimmt werden; Abbruch."
		return $E_LCKEYFPR
	fi
	$gpg_cmd --no-auto-check-trustdb --armor --export "$lckeyfingerprint" > "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}/$LCKEYFILE_PUBLIC"
	$gpg_cmd --no-auto-check-trustdb --armor --export-secret-keys "$lckeyfingerprint" > "$LCKEYFILE_SECRET"
	$gpg_cmd --no-auto-check-trustdb --armor --recipient "$x_short_id" --output "$LCKEYFILE_SECRET".asc --encrypt "$LCKEYFILE_SECRET"
	if [ -f "$LCKEYFILE_SECRET".asc ]; then
		cp "$LCKEYFILE_SECRET".asc "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}"/
	else
		clear
		cat <<EOT
AKTION ERFORDERLICH: lckey-Export
======================================================================

Die verschlüsselte Datei '$LCKEYFILE_SECRET.asc' existiert nicht und kann 
deshalb nicht in $COPYFILESTODIR/$TARGET_SUBDIR_IMPORT kopiert werden. 
Bitte lösen Sie das Problem manuell in einer anderen Konsole.

Wenn Sie damit fertig sind: Weiter mit Return...
EOT
	fi
	
	$gpg_cmd --check-trustdb &>/dev/null
	clear
	cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

So sieht der lokale Zertifizierungsschlüssel aus (Passphrase ist '${GENERATIONPASSPHRASE}'):
EOT
	$gpg_cmd --list-keys "0x$lckeyfingerprint"
	echo "Weiter mit Return..."
	read cont
} # sub__lckeygen ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__lckey_configfile () {
	if [ no = "$startlinefound" ]; then
		awk -v lckeyfpr="0x$lckeyfingerprint" '{gsub("^.*_LCKEYREMOVE_",""); gsub("__LCKEYFINGERPRINT__",lckeyfpr); print;}' "${CONFIGFILE}" > "${CONFIGFILE}".lckey &&
			mv "${CONFIGFILE}".lckey "${CONFIGFILE}"
	else
		awk -v lckeyfpr="0x$lckeyfingerprint" -v startline="$startline_number" 'NR<=startline {print; next}; {gsub("^.*_LCKEYREMOVE_",""); gsub("__LCKEYFINGERPRINT__",lckeyfpr); print;}' "${CONFIGFILE}" > "${CONFIGFILE}".lckey &&
			mv "${CONFIGFILE}".lckey "${CONFIGFILE}"
	fi
} # sub__lckey_configfile ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__call_sign_cert_menu () {
	local menuitems=(lckey lsign cleartext detached quit)
        while true; do
                sub__sign_cert_menu && break
        done
} # sub__call_sign_cert_menu ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__sign_cert_menu () {
	local auswahl
	clear
	cat <<EOT
AKTION ERFORDERLICH: Funktionsauswahl
======================================================================

Sie können jetzt

1) lckey: einen lokalen Zertifizierungsschlüssel erstellen
2) lsign: öffentliche Schlüssel zertifizieren
3) cleartext: (Hauptschlüssel-)Klartextsignaturen erzeugen
4) detached: abgetrennte (Hauptschlüssel-)Signaturen erzeugen

Nach jeder Aktion kommen Sie wieder zu dieser Auswahl.

Wenn Sie alles erledigt haben (oder nichts davon machen möchten), beenden 
Sie das Menü (quit).

===============================
Empfehlung: (1) und (2)
===============================

EOT
	PS3="Ihre Auswahl: "
	select auswahl in "${menuitems[@]}" ; do
		case "$auswahl" in
			*lckey)
				if $gpg_cmd -K lckey-lsign &>/dev/null; then
					echo "Es existiert bereits ein Zertifikat 'lckey-lsign'."
					if ! yesno "Möchten Sie wirklich einen (weiteren) lckey generieren?"; then
						return 1
					fi
				fi
				sub__lckeygen
				sub__lckey_configfile
				menuitems[0]="done...lckey"
				return 1
			;;
			*lsign)
				sub__cert_keys
				menuitems[1]="done...lsign"
				return 1
			;;
			*cleartext)
				sub__clearsign
				menuitems[2]="done...cleartext"
				return 1
			;;
			*detached)
				sub__detachsign
				menuitems[3]="done...detached"
				return 1
			;;
			quit)
				return 0
			;;
		esac
	done

} # sub__sign_cert_menu ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__cert_keys () {
	local lsign_keys_sourcedir_path key_file key_fpr file_count comment
	if [ -d "${startpath}/../${CERTKEYSDEFAULTDIR}" ]; then
		lsign_keys_sourcedir_path="$(readlink -f "${startpath}/../${CERTKEYSDEFAULTDIR}")"
	else
		lsign_keys_sourcedir_path="$PWD/$CERTKEYSDEFAULTDIR"
		mkdir -p "$lsign_keys_sourcedir_path"
	fi
	file_count=0
	for key_file in "$lsign_keys_sourcedir_path"/*; do
		((file_count++))
	done
	clear
	cat <<EOT
AKTION ERFORDERLICH: Andere Schlüssel (lokal) zertifizieren?
======================================================================

Wenn Sie jetzt andere Schlüssel (lokal, d.h. mit lsign statt sign) 
mit Hilfe dieses Scripts zertifizieren möchten, dann sammeln Sie (vor 
der nächsten Eingabe) die öffentlichen Schlüsseldateien in folgendem 
Verzeichnis:

            '$lsign_keys_sourcedir_path'

Momentan befinden sich in diesem Verzeichnis ${file_count} Dateien.

Achtung: Das Script unterstützt derzeit nur einen Schlüssel pro Datei!

Sie können diesen Teil auch überspringen. 

EOT
	if ! yesno "Möchten Sie nun die Schlüssel in diesem Verzeichnis zertifizieren?"; then
		return 0
	fi
	if ! pushd "$lsign_keys_sourcedir_path" &>/dev/null; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann ignoriert werden)
======================================================================

Es gab ein Problem bei der Zertifizierung anderer Schlüssel. Diese Funktion 
muss leider übersprungen werden.
Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
		read cont
	
	fi
	for key_file in *; do
		clear
		echo "Jetzt wird die Datei '$key_file' bearbeitet:"
		if ! $gpg_cmd --ignore-time-conflict --list-only --list-packets "$key_file" | grep -q "^:public key packet:$"; then
			echo
			echo "Die Datei '$key_file' enthält offenbar keinen öffentlichen Schlüssel."
			echo "Sie wird daher übersprungen."
			echo
			echo "Weiter mit Return..."
			continue
		fi
		key_fpr="$($gpg_cmd --ignore-time-conflict --status-fd 3 --import "$key_file" 3>&1 &>/dev/null | awk '$2 == "IMPORT_OK" {print $4; exit;}')"
		if [ "${#key_fpr}" -ne 40 ]; then
			echo
			echo "Fehler beim Importversuch der Datei '$key_file'."
			echo "Weiter mit Return..."
			continue
		fi
		echo
		echo "Die Datei '$key_file' enthält folgenden Schlüssel:"
		echo
		$gpg_cmd --list-keys "$key_fpr"
		echo
		# TODO: Nachfrage: Diesen Schlüssel bearbeiten?
		if ! yesno "Möchten Sie diesen Schlüssel signieren?"; then
			continue
		fi
		echo
		if [ "yes" = "$verbose" ]; then
			cat <<EOT

Sie können einen Kommentar eingeben, etwa zu den Umständen der Zertifizierung 
(Art der Identitätsprüfung; Veranstaltung) oder der Sicherheit des zu 
zertifizierenden Schlüssels. Da dies lokale Signaturen sind, wird (unter 
normalen Umständen) niemand außer Ihnen diese Kommentare zu Gesicht bekommen.

Wenn Sie keinen Kommentar hinzufügen möchten, machen Sie eine leere Eingabe 
(ggf. vorgegebenen Text löschen).

Falls Sie einen Kommentar eingeben, wird eine entsprechende signature notation 
erzeugt, die Sie nur mit

gpg --list-options show-notations --list-sigs

sehen können ('list-options show-notations' ist Bestandteil der zu diesem 
Script gehörenden Konfigurationsdatei-Vorlage).

EOT
			echo -n "optionaler Kommentar:"
			read -e -i "$comment" comment
		else
			comment=
		fi
		if [ no = "$passphrase_entered" ]; then
			cat <<EOT

Geben Sie gleich die Passphrase '${GENERATIONPASSPHRASE}' ein, um den 
geheimen Schlüssel zu entsperren. Nach dem Durchlaufen des 
Zertfizierungsdialogs geben Sie am gpg-Prompt

gpg> 

das Kommando "save" ein, um den veränderten Schlüssel zu speichern - oder
"quit", wenn Sie den jeweiligen Schlüssel überspringen wollen.


Weiter mit Return...

EOT
		else
			cat <<EOT

Geben Sie gleich Ihre Passphrase ein, um den geheimen Schlüssel zu 
entsperren. Nach dem Durchlaufen des Zertfizierungsdialogs geben Sie 
am gpg-Prompt

gpg> 

das Kommando "save" ein, um den veränderten Schlüssel zu speichern - oder
"quit", wenn Sie den jeweiligen Schlüssel doch überspringen wollen.


Weiter mit Return...

EOT
		fi
		read cont
		if [ -n "$comment" ]; then
			if $gpg_cmd --ignore-time-conflict --local-user "$x_short_id" --cert-notation comment@openpgp-notations.org="$comment" --edit-key 0x"$key_fpr" lsign; then
				$gpg_cmd --ignore-time-conflict --armor --export-options export-local-sigs --export 0x"$key_fpr" >~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_CERTS}"/0x"$key_fpr".public-certified.asc
			fi
		else
			if $gpg_cmd --ignore-time-conflict --local-user "$x_short_id" --edit-key 0x"$key_fpr" lsign; then
				$gpg_cmd --ignore-time-conflict --armor --export-options export-local-sigs --export 0x"$key_fpr" >~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_CERTS}"/0x"$key_fpr".public-certified.asc
			fi
		fi
	done
	popd &>/dev/null
} # sub__cert_keys ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__clearsign () {
	local clearsign_sourcedir_path="${startpath}/${CLEARSIGNDEFAULTSUBDIR}" textfile file_count
	if ! [ -d "$clearsign_sourcedir_path" ]; then
		return 0
	fi
	for key_file in "$clearsign_sourcedir_path"/*; do
		((file_count++))
	done
	clear
	cat <<EOT
AKTION ERFORDERLICH: Klartextsignaturen erzeugen?
======================================================================

Wenn Sie jetzt für Textdateien (z.B. OTR-Fingerprints) Klartextsignaturen 
durch den Hauptschlüssel erzeugen lassen wollen, dann sammeln Sie diese
Textdateien in folgendem Verzeichnis:

            '$clearsign_sourcedir_path'

Momentan befinden sich in diesem Verzeichnis ${file_count} Dateien.

Die meisten Nutzer benötigen diese Funktion nicht.

EOT
	if ! yesno "Möchten Sie nun Klartextsignaturen erzeugen?"; then
		return 0
	fi
	if ! pushd "$clearsign_sourcedir_path" &>/dev/null; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann ignoriert werden)
======================================================================

Es gab ein Problem bei der Erzeugung der Klartextsignaturen, muss leider 
übersprungen werden. Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
		read cont
	fi
	for textfile in *; do
		echo "Jetzt wird die Datei '$textfile' bearbeitet:"
		if [ -f ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}/${textfile}.asc" ]; then
			cat <<EOT


FEHLER (kein Abbruch, kann ignoriert werden)
======================================================================

Die Datei '${textfile}.asc' besteht bereits. Die Signatur kann nur erzeugt 
werden, wenn Sie diese Datei jetzt löschen (bevor Sie Return drücken).

Weiter mit Return...
EOT
			read cont
		fi
		if $gpg_cmd --batch --passphrase "$PASSPHRASE" --output ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}/${textfile}.asc" --local-user "${x_short_id}"\! --clearsign "$textfile"; then
			echo "Klartextsignatur für '${textfile}' erzeugt."
		else
			echo "FEHLER: Klartextsignatur für '${textfile}' konnte nicht erzeugt werden."
		fi
		echo
	done
	popd &>/dev/null
	echo
	echo "Weiter mit Return..."
	read cont
} # sub__clearsign ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__detachsign () {
	local detachsign_sourcedir_path="${startpath}/${DETACHSIGNDEFAULTSUBDIR}" file file_count
	if ! [ -d "$detachsign_sourcedir_path" ]; then
		return 0
	fi
	for key_file in "$detachsign_sourcedir_path"/*; do
		((file_count++))
	done
	clear
	cat <<EOT
AKTION ERFORDERLICH: abgetrennte Signaturen erzeugen?
======================================================================

Wenn Sie jetzt für beliebige Dateien (z.B. Schlüsselrichtlinie) abgetrennte 
Signaturen durch den Hauptschlüssel erzeugen lassen wollen, dann sammeln 
Sie diese Dateien in folgendem Verzeichnis:

            '$detachsign_sourcedir_path'

Momentan befinden sich in diesem Verzeichnis ${file_count} Dateien.

Die meisten Nutzer benötigen diese Funktion nicht.

EOT
	if ! yesno "Möchten Sie nun abgetrennte Signaturen erzeugen?"; then
		return 0
	fi
	if ! pushd "$detachsign_sourcedir_path" &>/dev/null; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann ignoriert werden)
======================================================================

Es gab ein Problem bei der Erzeugung der Signaturen, muss leider 
übersprungen werden. Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
		read cont
	fi
	for file in *; do
		echo "Jetzt wird die Datei '$file' bearbeitet:"
		if [ -f ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}/${file}.asc" ]; then
			cat <<EOT


FEHLER (kein Abbruch, kann ignoriert werden)
======================================================================

Die Datei '${file}.asc' besteht bereits. Die Signatur kann nur erzeugt 
werden, wenn Sie diese Datei jetzt löschen (bevor Sie Return drücken).

Weiter mit Return...
EOT
			read cont
		fi
		if $gpg_cmd --batch --passphrase "$PASSPHRASE" --output ~/.gnupg/"${COPYFILESTODIR}/${TARGET_SUBDIR_SIGS}/${file}.asc" --local-user "${x_short_id}"\! --detach-sign "$file"; then
			echo "Abgetrennte Signatur für '${file}' erzeugt."
		else
			echo "FEHLER: Abgetrennte Signatur für '${file}' konnte nicht erzeugt werden."
		fi
		echo
	done
	popd &>/dev/null
	echo
	echo "Weiter mit Return..."
	read cont
} # sub__detachsign ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__export_clean () {
	# COMMENT: überflüssige Signaturen löschen
	$gpg_cmd --check-trustdb &>/dev/null
	echo clean$'\n'save$'\n' | LC_ALL= LC_MESSAGES=C $gpg_cmd --batch --command-fd 0 --no-auto-check-trustdb --edit-key "$x_short_id"
	$gpg_cmd --check-trustdb &>/dev/null
	echo
	if [ error = "$(gpg --fixed-list-mode --with-colons --list-sigs "$x_short_id" | awk -F: '$1 != "sig" { sigcount=0; next; }; $1 == "sig" { sigcount++; if (sigcount>1) { print "error"; exit; }; };')" ]; then
	clear
		cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Der Schlüssel hat offenbar mehr als nur eine Eigenbeglaubigung, das sollte 
nicht passieren können. Manuell korrigieren, falls gewünscht, ist ein eher 
kosmetisches Problem.

Weiter mit Return...
EOT
	read cont
	fi
	echo "INFO: erneuter temporärer Export des privaten Hauptschlüssels (saubere Version)"
	$gpg_cmd --check-trustdb &>/dev/null
	$gpg_cmd --no-auto-check-trustdb --export-secret-keys "${x_short_id}" > "$KEYFILE_SECRET_MAINKEY_TMP"
	sleep 1
} # sub__export_clean ()


###################################################################################################

sub__export_mainpub () {
	local path path_mainonly path_sub
	echo "INFO: Export des öffentlichen Hauptschlüssels"
	$gpg_cmd --armor --no-auto-check-trustdb --export $x_short_id > "$keyfile_public"
	# BEGIN: create directories (one with and one without the subkeys) with pubring.gpg, secring.gpg and gpg.conf
	path="${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/${EXPORT_OPENPGP_DIR_SUB}"
	path_sub="$path"
	test -d "$path" || mkdir "$path" || exit 1
	cp "$CONFIGFILE" "$path"
	pwd
	set -x
	cp "${startpath}/../README.txt" "$path"
	set +x
	$gpg_cmd --homedir ./"$path" --import "$keyfile_public" &>/dev/null
	$gpg_cmd --homedir ./"$path" --trusted-key "${x_long_id}" --list-keys &>/dev/null
	$gpg_cmd --homedir ./"$path" --check-trustdb &>/dev/null

	path="${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/${EXPORT_OPENPGP_DIR_MAIN}"
	path_mainonly="$path"
	test -d "$path" || mkdir "$path" || exit 1
	cp "$CONFIGFILE" "$path"
	cp "${startpath}/../README.txt" "$path"

	# pubring.gpg without subkeys gets imported in sub__export_mainseconly into this directory
	cp "$path_sub"/trustdb.gpg "$path"
	# END:   create directories (one with and one without the subkeys) with pubring.gpg, secring.gpg and gpg.conf
	sleep 1
	echo
} # sub__export_mainpub ()

###################################################################################################

sub__export_mainsig () {
	echo "INFO: Hauptschlüssel-Signatur für den exportierten öffentlichen Hauptschlüssel erzeugen."
	$gpg_cmd --batch --passphrase $EXPORTPASSPHRASE --no-auto-check-trustdb --armor --local-user ${x_short_id}\! --detach-sign "$keyfile_public"
		sleep 2
} # sub__export_mainsig ()

###################################################################################################

sub__show_passphrase () {
	$gpg_cmd --check-trustdb &>/dev/null
	while true; do
		clear
		cat <<EOT
AKTION ERFORDERLICH: Generierung von Passphrase-Vorschlägen
======================================================================

Hinweis: Sicher sind 18 Stellen von [0-9a-zA-Z], also Ziffern, Klein- und 
Großbuchstaben (ohne Umlaute, Sonderzeichen und sonstige Problemquellen), 
also zum Beispiel die folgende zufällig generierten Zeichenfolge 
(gpg --armor --gen-random 1 n).

18 Stellen können im OpenPGP-Kontext (mit --s2k-count 1000000) als sicher 
angenommen werden. Wird die Passphrase ohne Iterationen verwendet, sollten 
22 Zeichen verwendet werden.

            Passphrase 1: $(print_passphrase $passphrase_length)

            Passphrase 2: $(print_passphrase $passphrase_length)
            

Wenn Sie dieses sichere Passwort übernehmen, dann sorgen Sie dafür, dass es 
niemand außer Ihnen am Bildschirm lesen kann! Auch nach dem Ende des Programms 
nicht! Beenden Sie also hinterher die Konsole (oder starten den Rechner 
neu).

Durch Drücken der Leertaste (statt Return) wird ein neuer Passwortvorschlag 
generiert.


Leertaste für eine neue Passphrase...
        oder
Weiter mit Return... (zum nächsten Programmteil)
EOT
		IFS= read -n 1 cont
		if [ " " = "$cont" ]; then
			continue
		else
			echo
			if yesno "Sind Sie mit der Passphrase-Erzeugung fertig?"; then
				break
			fi
		fi
	done
	clear
} # sub__show_passphrase ()

###################################################################################################

sub__export_setmainpw () {
	$gpg_cmd --check-trustdb &>/dev/null
	while true; do
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Jetzt die sichere Passphrase für den Hauptschlüssel setzen.

Hinweis: Sicher sind 18 Stellen von [0-9a-zA-Z], also Ziffern, Klein- und 
Großbuchstaben (ohne Umlaute, Sonderzeichen und sonstige Problemquellen), 
also zum Beispiel die folgende zufällig generierten Zeichenfolge 
(gpg --armor --gen-random 1 n):

            Hauptschlüssel: $(print_passphrase 18)

            Transport:      $(print_passphrase 18)
            
                     BEIDE AUFSCHREIBEN!

Wenn Sie dieses sichere Passwort übernehmen (d.h. abschreiben; dieses Script 
kann leider die Passphrase nicht selber setzen), sorgen Sie dafür, dass es 
niemand außer Ihnen am Bildschirm lesen kann! Auch nach dem Ende des Programms 
nicht! Beenden Sie also hinterher die Konsole (oder starten den Rechner 
neu). Durch Drücken der Leertaste (statt Return) wird ein neuer 
Passwortvorschlag generiert.

Wenn Sie keine von diesem Script generierte Passphrase nehmen, sollten Sie 
darauf achten, keine verwechselbaren Zeichen (1IlkKO0) zu verwenden, weil 
es ziemlich lästig ist, in einer langen Passphrase den Fehler zu suchen...

Wenn die Hauptschlüssel-Passphrase schlecht gewählt wird, ist die ganze 
Aktion weitgehend sinnlos. Wird sie vergessen (bzw. verloren), ist ein 
neuer Schlüssel fällig!

Leertaste für eine neue Passphrase...
        oder
Weiter mit Return... (zum nächsten Programmteil)
EOT
		IFS= read -n 1 cont
		if [ " " = "$cont" ]; then
			continue
		else
			echo
			if ! yesno "Sind Sie mit der Passphrase-Erzeugung fertig?"; then
				continue
			fi
		fi
		clear
		cat <<EOT
AKTION ERFORDERLICH: Passphrase für den Hauptschlüssel
======================================================================

Leider ist die nächste Aktion etwas unübersichtlich. Das folgende gut merken:

1) Zuerst die alte Passphrase ($EXPORTPASSPHRASE) eingeben!

2) Dann zweimal die neue.

3) Nach der Eingabe der Passphrase am gpg-Prompt (gpg> ) 'save' eingeben!


Sobald verstanden, weiter mit Return...
EOT
		read cont
		if ! $gpg_cmd --edit-key $x_short_id passwd 2>/dev/null; then
			clear
			cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

gpg wurde gerade aufgerufen, um die Passphrase zu ändern, hat sich aber 
mit einem Fehlercode beendet (oder wurde beendet). Das hat im allgemeinen 
nichts Gutes zu bedeuten. Sie können den Aufruf nun wiederholen oder das 
Script weiterlaufen lassen, wenn Sie wissen, was Sie tun.

EOT
			if ! yesno "Wollen Sie die letzte Aktion wiederholen?"; then
				break
			fi
		else
			if echo fubar | $gpg_cmd --batch --passphrase "$GENERATIONPASSPHRASE" --no-auto-check-trustdb --armor --local-user ${x_short_id}\! --detach-sign testfile.txt &>/dev/null; then
				clear
				cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

gpg wurde gerade aufgerufen, um die Passphrase zu ändern, es ist aber immer 
noch die ursprüngliche Passphrase aktiv. Der Vorgang wird wiederholt.

Weiter mit Return...
EOT
				read cont
				continue
			fi
			break
		fi
	done
} # sub__export_setmainpw ()

###################################################################################################

sub__export_mainsec () {
	echo "INFO: regulärer Export des geheimen Hauptschlüssels"
	# COMMENT: export to file
	$gpg_cmd --armor --export-secret-keys $x_short_id > "$keyfile_secret_mainkey"
	# COMMENT: export to directory ($EXPORT_OPENPGP_DIR_SUB)
	$gpg_cmd --homedir "${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/${EXPORT_OPENPGP_DIR_SUB}" --import "$keyfile_secret_mainkey" &>/dev/null
} # sub__export_mainsec ()

###################################################################################################

sub__export_mainseconly () {
	# COMMENT: delete all subkeys for mainkey-only export
	subkeys_count="$(gpg --with-colons --list-keys $x_short_id | grep -c ^sub:)"
	for((i=0; i<subkeys_count; i++)); do
		echo "key 1"$'\n'delkey$'\n'y$'\n'save$'\n' | LC_MESSAGES=C $gpg_cmd --batch --utf8-strings --passphrase "$GENERATIONPASSPHRASE" --command-fd 0 --no-auto-check-trustdb --edit-key "$x_short_id" &>/dev/null
	done
	echo "INFO: Export des geheimen Hauptschlüssels ohne die Unterschlüssel"
	$gpg_cmd --armor --export-secret-keys $x_short_id > "$keyfile_secret_mainkey_only"
	$gpg_cmd --homedir "${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/${EXPORT_OPENPGP_DIR_MAIN}" --import "$keyfile_secret_mainkey_only" &>/dev/null
	echo "INFO: Export des öffentlichen Hauptschlüssels ohne die Unterschlüssel"
	$gpg_cmd --armor --export $x_short_id > "$keyfile_public_mainkeyonly"
	$gpg_cmd --homedir "${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/${EXPORT_OPENPGP_DIR_MAIN}" --import "$keyfile_public_mainkeyonly" &>/dev/null
} # sub__export_mainseconly ()

###################################################################################################

sub__export_resetpw () {
	### Passphrase resetten (Tipparbeit reduzieren)
	echo
	echo "INFO: Löschen aller geheimen Schlüssel"
	$gpg_cmd --batch --delete-secret-key $x_fingerprint &>/dev/null
	echo
	echo "INFO: Import aller geheimen Hauptschlüssels (mit der alten Passphrase)"
	$gpg_cmd --import-options import-minimal --import "$KEYFILE_SECRET_MAINKEY_TMP" &>/dev/null
} # sub__export_resetpw ()

###################################################################################################

sub__export_setsubpw () {
	$gpg_cmd --check-trustdb &>/dev/null
	while true; do
		clear
		cat <<EOT
AKTION ERFORDERLICH: Passphrase für die Unterschlüssel setzen
======================================================================

Leider ist die nächste Aktion etwas unübersichtlich. Das folgende gut merken:

1) Zuerst die alte Passphrase ($EXPORTPASSPHRASE) eingeben!

2) Dann zweimal die neue.

3) Nach der Eingabe der Passphrase am gpg-Prompt (gpg> ) 'save' eingeben!


Sobald verstanden, weiter mit Return...
EOT
		read cont
		if ! $gpg_cmd --edit-key $x_short_id passwd 2>/dev/null; then
			clear
			cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

gpg wurde gerade aufgerufen, um die Passphrase zu ändern, hat sich aber 
mit einem Fehlercode beendet (oder wurde beendet). Das hat im allgemeinen 
nichts Gutes zu bedeuten. Sie können den Aufruf nun wiederholen oder das 
Script weiterlaufen lassen, wenn Sie wissen, was Sie tun.

EOT
			if ! yesno "Wollen Sie die letzte Aktion wiederholen?"; then
				break
			fi
		else
			echo foo > testfile.txt
			if $gpg_cmd --batch --passphrase $EXPORTPASSPHRASE --no-auto-check-trustdb --armor --local-user ${x_short_id} --detach-sign testfile.txt; then
				clear
				cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

gpg wurde gerade aufgerufen, um die Passphrase zu ändern, es ist aber immer 
noch die ursprüngliche Passphrase aktiv. Der Vorgang wird wiederholt.

Weiter mit Return...
EOT
				read cont
				continue
			fi
			break
		fi
	done
} # sub__export_setsubpw ()

###################################################################################################

sub__export_subsec () {
	echo "INFO: regulärer Export der privaten Unterschlüssel"
	$gpg_cmd --armor --export-secret-subkeys $x_short_id > "$keyfile_secret_subkeys"
	# COMMENT: The following check occurs earlier in this function, too. At this position it shall 
	#	detect errors by manual intervention during the export (after problems).
	if [ error = "$(gpg --fixed-list-mode --with-colons --list-sigs "$x_short_id" | awk -F: '$1 != "sig" { sigcount=0; next; }; $1 == "sig" { sigcount++; if (sigcount>1) { print "error"; exit; }; };')" ]; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Der Schlüssel hat offenbar mehr als nur eine Eigenbeglaubigung, das sollte 
nicht passieren können. Manuell korrigieren, falls gewünscht, ist ein eher 
kosmetisches Problem.

Weiter mit Return...
EOT
		read cont
	fi
} # sub__export_subsec ()

###################################################################################################

###################################################################################################

sub__export () {
	local subkeys_count i
	local run_var run_vars run_function
	clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Als nächstes wird der Schlüssel (der geheime und der öffentliche) exportiert.
Wenn Sie per Hand noch Änderungen an dem Schlüssel vornehmen wollen, die 
dieses Script nicht kann, dann machen Sie das bitte jetzt und starten 
anschließend den Export.

Weiter mit Return...
EOT
	read cont
	echo
	run_vars=(run__sub__export_clean \
	run__sub__export_mainpub \
	run__sub__export_mainsig \
	run__sub__export_setmainpw \
	run__sub__export_mainsec \
	run__sub__export_mainseconly \
	run__sub__export_resetpw \
	run__sub__export_setsubpw \
	run__sub__export_subsec)
	for run_var in "${run_vars[@]}"; do
		if [ -n "${!run_var}" ]; then
			run_function="${run_var#run__}"
			"$run_function"
		fi
	done
	echo
	set +e
} # sub__export ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__deltmp () {
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Sobald Sie Return drücken, werden

	a) der geheime Keyring
	b) die temporäre Exportdatei der privaten Schlüssel

gelöscht.

Das ist die übliche Aktion an dieser Stelle. Nur für den Fall, dass 
im Scriptablauf spezielle Fehler aufgetreten sind, die jetzt noch mit 
Hilfe der temporären Exportdatei manuell behoben werden müssen, wurde 
dieser Haltepunkt eingefügt.

Wenn also bisher nichts Schlimmes passiert ist, drücken Sie Return.

ACHTUNG: Die Dateien wurden noch nicht ins Zielverzeichnis kopiert!
Das Script also auch im Reparaturfall hier nicht abbrechen!

Weiter mit Return...
EOT
	read cont
	if ! wipe_file "$KEYFILE_SECRET_MAINKEY_TMP"; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Es gab ein Problem bei der sicheren Löschung von '$KEYFILE_SECRET_MAINKEY_TMP'.
Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
		read cont
	fi
	wipe_file random_seed &>/dev/null
	if ! wipe_file secring.gpg; then
		clear
		cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Es gab ein Problem bei der sicheren Löschung von 'secring.gpg'.
Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
		read cont
	else
		clear
		cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Der geheime Keyring wurde überschrieben und gelöscht.
Wenn Sie noch etwas mit dem Schlüssel machen wollen, müssen Sie die 
Datei '${keyfile_secret_mainkey}', '${keyfile_secret_mainkey_only}', 
oder (für "normale" Operationen, die man aber auch später im 
Arbeitssystem durchführen kann) '${keyfile_secret_subkeys}' importieren 
(gpg --import). Vergessen Sie dann aber nicht, den Keyring erneut sicher 
zu löschen!

Weiter mit Return...
EOT
	read cont
	fi
} # sub__deltmp ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__delwarning () {
	clear
	cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Warnung:
Achten Sie darauf, nach der Arbeit mit diesem Script ggf. die geheimen 
Schlüssel zu sichern! Wenn dies unter einer Live-CD läuft, dann können Sie 
zu diesem Zweck z.B. den Rechner neu starten oder ausschalten.

Oder Sie löschen die geheimen Schlüssel aus dem Keyring:
gpg --delete-secret-key 0x12345678

Weiter mit Return...
EOT
	read cont
} # sub__delwarning ()

###################################################################################################
###################################################################################################
###################################################################################################

sub__copy () {
	local file
	echo "INFO: Dateien werden ins Zielverzeichnis kopiert:"
	# COMMENT: Copy this script so that the user (who should have documented the script's hash 
	#	before execution) can later examine the script in order to prove the security
	#	of the key.
	cp "$keyfile_secret_subkeys" "$keyfile_public" "$keyfile_public".asc "$CONFIGFILE" "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}"/
	for file in "${startpath}/${IMPORT_UNIX}" "${startpath}/${IMPORT_WIN}" "${startpath}/${TEXTSIGNATURE_FILE}"; do
		if [ -f "$file" ]; then
			cp "$file" "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}"/
		fi
	done
	cp "$keyfile_secret_mainkey" "$keyfile_secret_mainkey_only" "${COPYFILESTODIR}/${TARGET_SUBDIR_SECENV}/"
	cp "$SCRIPTCOPYNAME" pubring.gpg "${COPYFILESTODIR}/${TARGET_SUBDIR_MISC}/"
	cp "${startpath}/$IMPORT_CERTS_WIN" "${startpath}/$IMPORT_CERTS_UNIX" "${COPYFILESTODIR}/${TARGET_SUBDIR_CERTS}/"

	for file in "${startpath}/README.txt"; do
		if [ -f "$file" ]; then
			cp "$file" "$COPYFILESTODIR"/
		fi
	done

	$gpg_cmd --export-ownertrust > "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}/${OWNERTRUSTEXPORTFILE}"
	# COMMENT: Versionen für Windows (\r\n) erzeugen
	awk -v ORS=$'\r\n' 1 gpg.conf > "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}"/gpg.win.conf
	awk -v ORS=$'\r\n' 1 "${startpath}/README.txt" > "${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}/README.win.txt"
	echo -n "$SHORTID_FILE_TEXT" >"${COPYFILESTODIR}/${TARGET_SUBDIR_IMPORT}/${x_short_id}"

	# COMMENT: For the case that the target dir is on a *nix file system make the written files and dirs readable for everyone
	#	in order to avoid problems when copying the whole directory tree with a user account file manager after the script 
	#	has been executed as root.
	chmod -R a+rX "$COPYFILESTODIR"/ &>/dev/null
	chmod -R a+rw "$COPYFILESTODIR"/*.gpg &>/dev/null
	chmod -R 755 "$COPYFILESTODIR" &>/dev/null

	clear
	cat <<EOT
AKTION ERFORDERLICH: Alle Exportdateien vorhanden?
======================================================================

Prüfen Sie bitte, ob die Dateien
1: $keyfile_secret_mainkey
2: $keyfile_secret_mainkey_only
3: $keyfile_secret_subkeys
4: $keyfile_public
5: $keyfile_public.asc
6: $CONFIGFILE
7: $SCRIPTCOPYNAME
8: pubring.gpg
erfolgreich kopiert wurden (nach '$(readlink -f "$COPYFILESTODIR")').

Wenn Sie Return drücken, werden die Originale der kopierten Dateien mit 
geheimen Schlüsseln gelöscht; außerdem der Schlüssel im Keyring. Wenn Sie 
das nicht möchten (und wissen, was Sie tun...), dann brechen Sie dieses 
Script jetzt ab (mit ^C), anstatt es mit Return fortzusetzen.
Hoffentlich hatten Sie viel Freude mit diesen Script   :-)

http://www.openpgp-schulungen.de/

Weiter mit Return...
EOT
	read cont

	for file in "$keyfile_secret_mainkey" "$keyfile_secret_mainkey_only" "$keyfile_secret_subkeys" "$LCKEYFILE_SECRET" secring.gpg; do
		if ! [ -f "$file" ]; then
			continue
		fi
		echo "INFO: Überschreibe und lösche Datei '$file'"
		if ! wipe_file "$file"; then
			clear
			cat <<EOT
FEHLER (kein Abbruch, kann behoben werden)
======================================================================

Es gab ein Problem bei der sicheren Löschung von '$file'.
Bitte ggf. manuell erledigen.

Weiter mit Return...
EOT
			read cont
		fi
	done
} # sub__copy ()

###################################################################################################
###################################################################################################
###################################################################################################

if [ "yes" = "$debug_v" ]; then
	set -v
fi
if [ "yes" = "$debug_x" ]; then
	set -x
fi

# HAUPTPROGRAMM
: DEBUG: "$DEBUG"
# COMMENT: Parameter einlesen und entsprechende Subroutinen starten
while [ $# -gt 0 -o -z "$mode" ]; do
	parameter="$1"
	if [ $# -gt 0 ]; then
		shift
	else
		parameter="--all"
	fi
	case "$parameter" in
		--all)
			# COMMENT: Standardfall, alles abarbeiten
			mode=all
		;;
		--adapt-config)
			mode=adapt-config
			secmainkeyneeded=no
			if [ $# -gt 0 ]; then
				keyid="$1"
			fi
		;;
		--cert)
			mode=cert
			secmainkeyneeded=yes
			if [ $# -gt 0 ]; then
				keyid="$1"
			fi
		;;
		--sign)
			mode=sign
			secmainkeyneeded=yes
			if [ $# -gt 0 ]; then
				keyid="$1"
			fi
		;;
		--seed-file)
			if [ $# -gt 0 ]; then
				seedfile="$1"
				shift
			else
				echo "Fehlendes Argument zum Parameter '${parameter}'; Abbruch"
				usage
				exit $E_PARAM
			fi
			if ! [ -f "$seedfile" -a -r "$seedfile" ]; then
				echo "Argument zum Parameter '${parameter}' ist keine lesbare Datei; Abbruch"
				exit $E_PARAM
			fi
		;;
		--high-security)
			mainkeylength="$MAINKEYLENGTH_HISEC"
			subkeylength="$SUBKEYLENGTH_HISEC"
			expire_period="$EXPIRE_PERIOD_HISEC"
			securitylevel="high"
		;;
		--export)
			echo "Die Funktion '--export' ist noch nicht implementiert, sorry."
			exit 2
		;;
		--expire)
			echo "Die Funktion '--expire' ist noch nicht implementiert, sorry."
			exit 2
		;;
		--passphrase)
			mode=passphrase
			if [ $# -gt 0 ]; then
				if [[ $1 =~ ^[1-9][0-9]*$ ]]; then
					passphrase_length=$1
				fi
				shift
			fi
		;;
		"")
			: ignore
		;;
		*)
			echo "Ungültiger Parameter '${parameter}'; Abbruch"
			usage
			exit $E_PARAM
		;;
	esac
done
case "$mode" in
	all)
		# COMMENT: Standardfall, alles abarbeiten
		sub__verbosity
		run__sub__checks_norandmaps="1" # FIXME
		run__sub__checks_cd="1"
		run__sub__checks_gpgconf="1"
		run__sub__checks_keyconf="1"
		run__sub__checks_mvconfdir="1"
		run__sub__checks_netwconn="1"
		run__sub__checks_unsafewarning="1"
		run__sub__checks_initconfdir="1"
		run__sub__checks_copyscript="1"
		run__sub__checks_targetdir="1"
		run__sub__checks_configfileok="1"
		run__sub__checks_uid0name="1"
		run__sub__checks_agent="1"
		if [ yes = "$verbose" ]; then
			run__sub__checks_hash="1"
			run__sub__checks_time="1"
			run__sub__checks_screen="1"
			run__sub__checks_showscriptconf="1"
		fi
		sub__checks
		gpgagent_start
		sub__mainkeygen
		sub__set_vars
		sub__adapt_config
		sub__uids_subkeys
		sub__primary_uid
		sub__manual_changes
		sub__call_sign_cert_menu
		run__sub__export_clean="1"
		run__sub__export_mainpub="1"
		run__sub__export_mainsig="1"
		run__sub__export_setmainpw="1"
		run__sub__export_mainsec="1"
		run__sub__export_mainseconly="1"
		run__sub__export_resetpw="1"
		if [ "normal" = "$securitylevel" ]; then
			run__sub__export_setsubpw="1"
			run__sub__export_subsec="1"
		fi
		sub__export
		gpgagent_reset
		sub__deltmp
		sub__copy
	;;
	adapt-config)
		sub__verbosity
		sub__get_key "$keyid"
		fingerprint="$found_fingerprint"
		sub__checks
		sub__set_vars
		sub__adapt_config
	;;
	cert)
		sub__verbosity
		sub__get_key "$keyid"
		fingerprint="$found_fingerprint"
		sub__checks
		sub__set_vars
		sub__cert_keys
		sub__delwarning
	;;
	sign)
		sub__verbosity
		sub__get_key "$keyid"
		fingerprint="$found_fingerprint"
		sub__checks
		sub__set_vars
		sub__get_passphrase
		sub__clearsign
		sub__detachsign
		sub__delwarning
	;;
	passphrase)
		sub__show_passphrase
	;;
	*)
		echo "Interner Fehler; Abbruch"
		exit 1
	;;
esac

cat <<EOT



                                ENDE
======================================================================

             Das war's. Danke und bis zum nächsten Mal!

                     www.openpgp-schulungen.de
EOT
