diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/.gitignore | 1 | ||||
| -rw-r--r-- | scripts/Makefile.am | 5 | ||||
| -rw-r--r-- | scripts/makepkg.sh.in | 161 | ||||
| -rw-r--r-- | scripts/pacman-key.sh.in | 332 | ||||
| -rw-r--r-- | scripts/pkgdelta.sh.in | 2 | ||||
| -rw-r--r-- | scripts/repo-add.sh.in | 169 | 
6 files changed, 629 insertions, 41 deletions
| diff --git a/scripts/.gitignore b/scripts/.gitignore index fe4616f2..927b14c8 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -5,3 +5,4 @@ rankmirrors  repo-add  repo-remove  pkgdelta +pacman-key diff --git a/scripts/Makefile.am b/scripts/Makefile.am index ae6ce366..daa5b2ae 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -8,6 +8,7 @@ bin_SCRIPTS = \  OURSCRIPTS = \  	makepkg \  	pacman-db-upgrade \ +	pacman-key \  	pacman-optimize \  	pkgdelta \  	rankmirrors \ @@ -16,6 +17,7 @@ OURSCRIPTS = \  EXTRA_DIST = \  	makepkg.sh.in \  	pacman-db-upgrade.sh.in \ +	pacman-key.sh.in \  	pacman-optimize.sh.in \  	pkgdelta.sh.in \  	rankmirrors.sh.in \ @@ -45,7 +47,7 @@ edit = sed \  	-e 's|@SIZECMD[@]|$(SIZECMD)|g' \  	-e 's|@SEDINPLACE[@]|$(SEDINPLACE)|g' \  	-e 's|@DUPATH[@]|$(DUPATH)|g' \ -	-e 's|@configure_input[@]|Generated from $@.in; do not edit by hand.|g' +	-e 's|@configure_input[@]|Generated from $@.sh.in; do not edit by hand.|g'  ## All the scripts depend on Makefile so that they are rebuilt when the  ## prefix etc. changes. Use chmod -w to prevent people from editing the @@ -64,6 +66,7 @@ $(OURSCRIPTS): Makefile  makepkg: $(srcdir)/makepkg.sh.in  pacman-db-upgrade: $(srcdir)/pacman-db-upgrade.sh.in +pacman-key: $(srcdir)/pacman-key.sh.in  pacman-optimize: $(srcdir)/pacman-optimize.sh.in  pkgdelta: $(srcdir)/pkgdelta.sh.in  rankmirrors: $(srcdir)/rankmirrors.sh.in diff --git a/scripts/makepkg.sh.in b/scripts/makepkg.sh.in index 6fb36d27..9e0a667d 100644 --- a/scripts/makepkg.sh.in +++ b/scripts/makepkg.sh.in @@ -28,7 +28,7 @@  # makepkg uses quite a few external programs during its execution. You  # need to have at least the following installed for makepkg to function:  #   awk, bsdtar (libarchive), bzip2, coreutils, fakeroot, file, find (findutils), -#   gettext, grep, gzip, openssl, sed, tput (ncurses), xz +#   gettext, gpg, grep, gzip, openssl, sed, tput (ncurses), xz  # gettext initialization  export TEXTDOMAIN='pacman' @@ -75,6 +75,7 @@ CHECKFUNC=0  PKGFUNC=0  SPLITPKG=0  PKGLIST=() +SIGNPKG=''  # Forces the pkgver of the current PKGBUILD. Used by the fakeroot call  # when dealing with svn/cvs/etc PKGBUILDs. @@ -391,7 +392,7 @@ run_pacman() {  	local cmd  	printf -v cmd "%q " "$PACMAN" $PACMAN_OPTS "$@"  	if (( ! ASROOT )) && [[ ! $1 =~ ^-(T|Qq)$ ]]; then -		if [ "$(type -p sudo)" ]; then +		if type -p sudo >/dev/null; then  			cmd="sudo $cmd"  		else  			cmd="su root -c '$cmd'" @@ -855,7 +856,7 @@ tidy_install() {  	fi  	if [[ $(check_option purge) = "y" && -n ${PURGE_TARGETS[*]} ]]; then -		msg2 "$(gettext "Purging other files...")" +		msg2 "$(gettext "Purging unwanted files...")"  		local pt  		for pt in "${PURGE_TARGETS[@]}"; do  			if [[ ${pt} = ${pt//\/} ]]; then @@ -931,6 +932,58 @@ tidy_install() {  	fi  } +find_libdepends() { +	local libdepends +	find "$pkgdir" -type f -perm -u+x | while read filename +	do +		# get architecture of the file; if soarch is empty it's not an ELF binary +		soarch=$(LC_ALL=C readelf -h "$filename" 2>/dev/null | sed -n 's/.*Class.*ELF\(32\|64\)/\1/p') +		[ -n "$soarch" ] || continue +		# process all libraries needed by the binary +		for sofile in $(LC_ALL=C readelf -d "$filename" 2>/dev/null | sed -nr 's/.*Shared library: \[(.*)\].*/\1/p') +		do +			# extract the library name: libfoo.so +			soname="${sofile%%\.so\.*}.so" +			# extract the major version: 1 +			soversion="${sofile##*\.so\.}" +			if in_array "${soname}" ${depends[@]}; then +				if ! in_array "${soname}=${soversion}-${soarch}" ${libdepends[@]}; then +					# libfoo.so=1-64 +					echo "${soname}=${soversion}-${soarch}" +					libdepends=(${libdepends[@]} "${soname}=${soversion}-${soarch}") +				fi +			fi +		done +	done +} + +find_libprovides() { +	local libprovides +	find "$pkgdir" -type f -name \*.so\* | while read filename +	do +		# check if we really have a shared object +		if LC_ALL=C readelf -h "$filename" 2>/dev/null | grep -q '.*Type:.*DYN (Shared object file).*'; then +			# 64 +			soarch=$(LC_ALL=C readelf -h "$filename" | sed -n 's/.*Class.*ELF\(32\|64\)/\1/p') +			# get the string binaries link to: libfoo.so.1.2 -> libfoo.so.1 +			sofile=$(LC_ALL=C readelf -d "$filename" 2>/dev/null | sed -n 's/.*Library soname: \[\(.*\)\].*/\1/p') +			[ -z "$sofile" ] && sofile="${$filename##*/}" + +			# extract the library name: libfoo.so +			soname="${sofile%%\.so\.*}.so" +			# extract the major version: 1 +			soversion="${sofile##*\.so\.}" +			if in_array "${soname}" ${provides[@]}; then +				if ! in_array "${soname}=${soversion}-${soarch}" ${libprovides[@]}; then +					# libfoo.so=1-64 +					echo "${soname}=${soversion}-${soarch}" +					libprovides=(${libprovides[@]} "${soname}=${soversion}-${soarch}") +				fi +			fi +    fi +	done +} +  write_pkginfo() {  	local builddate=$(date -u "+%s")  	if [[ -n $PACKAGER ]]; then @@ -960,13 +1013,44 @@ write_pkginfo() {  	[[ $license ]]    && printf "license = %s\n"   "${license[@]}"  	[[ $replaces ]]   && printf "replaces = %s\n"  "${replaces[@]}"  	[[ $groups ]]     && printf "group = %s\n"     "${groups[@]}" -	[[ $depends ]]    && printf "depend = %s\n"    "${depends[@]}"  	[[ $optdepends ]] && printf "optdepend = %s\n" "${optdepends[@]}"  	[[ $conflicts ]]  && printf "conflict = %s\n"  "${conflicts[@]}" -	[[ $provides ]]   && printf "provides = %s\n"  "${provides[@]}"  	[[ $backup ]]     && printf "backup = %s\n"    "${backup[@]}"  	local it + +	libprovides=$(find_libprovides) +	libdepends=$(find_libdepends) +	provides=("${provides[@]}" ${libprovides}) +	depends=("${depends[@]}" ${libdepends}) + +	for it in "${depends[@]}"; do +		if [[ $it = *.so ]]; then +			# check if the entry has been found by find_libdepends +			# if not, it's unneeded; tell the user so he can remove it +			if [[ ! $libdepends =~ (^|\s)${it}=.* ]]; then +				error "$(gettext "Can't find library listed in \$depends: %s")" "$it" +				return 1 +			fi +		else +			echo "depend = $it" +		fi +	done + +	for it in "${provides[@]}"; do +		# ignore versionless entires (those come from the PKGBUILD) +		if [[ $it  = *.so ]]; then +			# check if the entry has been found by find_libprovides +			# if not, it's unneeded; tell the user so he can remove it +			if [[ ! $libprovides =~ (^|\s)${it}=.* ]]; then +				error "$(gettext "Can't find library listed in \$provides: %s")" "$it" +				return 1 +			fi +		else +			echo "provides = $it" +		fi +	done +  	for it in "${packaging_options[@]}"; do  		local ret="$(check_option $it)"  		if [[ $ret != "?" ]]; then @@ -1065,6 +1149,9 @@ create_package() {  	local pkg_file="$PKGDEST/${nameofpkg}-${fullver}-${PKGARCH}${PKGEXT}"  	local ret=0 +	[[ -f $pkg_file ]] && rm -f "$pkg_file" +	[[ -f $pkg_file.sig ]] && rm -f "$pkg_file.sig" +  	# when fileglobbing, we want * in an empty directory to expand to  	# the null string rather than itself  	shopt -s nullglob @@ -1086,9 +1173,12 @@ create_package() {  		exit 1 # TODO: error code  	fi +	create_signature "$pkg_file" +  	if (( ! ret )) && [[ ! "$PKGDEST" -ef "${startdir}" ]]; then  		ln -sf "${pkg_file}" "${pkg_file/$PKGDEST/$startdir}"  		ret=$? +		[[ -f $pkg_file.sig ]] && ln -sf "$pkg_file.sig" "${pkg_file/$PKGDEST/$startdir}.sig"  	fi  	if (( ret )); then @@ -1096,6 +1186,33 @@ create_package() {  	fi  } +create_signature() { +	if [[ $SIGNPKG != 'y' ]]; then +		return +	fi +	local ret=0 +	local filename="$1" +	msg "$(gettext "Signing package...")" +	if ! type -p gpg >/dev/null; then +		error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")" +		exit 1 # $E_MISSING_PROGRAM +	fi + +	local SIGNWITHKEY="" +	if [[ -n $GPGKEY ]]; then +		SIGNWITHKEY="-u ${GPGKEY}" +	fi +	# The signature will be generated directly in ascii-friendly format +	gpg --detach-sign --use-agent ${SIGNWITHKEY} "$filename" &>/dev/null || ret=$? + + +	if (( ! ret )); then +		msg2 "$(gettext "Created signature file %s.")" "$filename.sig" +	else +		warning "$(gettext "Failed to sign package file.")" +	fi +} +  create_srcpackage() {  	cd "$startdir" @@ -1573,7 +1690,7 @@ usage() {  	echo "$(gettext "  -e, --noextract  Do not extract source files (use existing src/ dir)")"  	echo "$(gettext "  -f, --force      Overwrite existing package")"  	echo "$(gettext "  -g, --geninteg   Generate integrity checks for source files")" -	echo "$(gettext "  -h, --help       This help")" +	echo "$(gettext "  -h, --help       Show this help message and exit")"  	echo "$(gettext "  -i, --install    Install package after successful build")"  	echo "$(gettext "  -L, --log        Log package build process")"  	echo "$(gettext "  -m, --nocolor    Disable colorized output messages")" @@ -1587,8 +1704,11 @@ usage() {  	printf "$(gettext "  --check          Run the check() function in the %s")\n" "$BUILDSCRIPT"  	printf "$(gettext "  --config <file>  Use an alternate config file (instead of '%s')")\n" "$confdir/makepkg.conf"  	printf "$(gettext "  --holdver        Prevent automatic version bumping for development %ss")\n" "$BUILDSCRIPT" +	echo "$(gettext "  --key <key>      Specify a key to use for gpg signing instead of the default")"  	printf "$(gettext "  --nocheck        Do not run the check() function in the %s")\n" "$BUILDSCRIPT" +	echo "$(gettext "  --nosign         Do not create a signature for the package")"  	echo "$(gettext "  --pkg <list>     Only build listed packages from a split package")" +	echo "$(gettext "  --sign           Sign the resulting package with gpg")"  	echo "$(gettext "  --skipinteg      Do not fail when integrity checks are missing")"  	echo "$(gettext "  --source         Generate a source-only tarball without downloaded sources")"  	echo @@ -1625,8 +1745,8 @@ ARGLIST=("$@")  OPT_SHORT="AcCdefFghiLmop:rRsV"  OPT_LONG="allsource,asroot,ignorearch,check,clean,cleancache,nodeps"  OPT_LONG+=",noextract,force,forcever:,geninteg,help,holdver" -OPT_LONG+=",install,log,nocolor,nobuild,nocheck,pkg:,rmdeps" -OPT_LONG+=",repackage,skipinteg,source,syncdeps,version,config:" +OPT_LONG+=",install,key:,log,nocolor,nobuild,nocheck,nosign,pkg:,rmdeps" +OPT_LONG+=",repackage,skipinteg,sign,source,syncdeps,version,config:"  # Pacman Options  OPT_LONG+=",noconfirm,noprogressbar"  OPT_TEMP="$(parse_options $OPT_SHORT $OPT_LONG "$@" || echo 'PARSE_OPTIONS FAILED')" @@ -1660,15 +1780,18 @@ while true; do  		-g|--geninteg)    GENINTEG=1 ;;  		--holdver)        HOLDVER=1 ;;  		-i|--install)     INSTALL=1 ;; +		--key)            shift; GPGKEY=$1 ;;  		-L|--log)         LOGGING=1 ;;  		-m|--nocolor)     USE_COLOR='n' ;;  		--nocheck)        RUN_CHECK='n' ;; +		--nosign)         SIGNPKG='n' ;;  		-o|--nobuild)     NOBUILD=1 ;;  		-p)               shift; BUILDFILE=$1 ;;  		--pkg)            shift; PKGLIST=($1) ;;  		-r|--rmdeps)      RMDEPS=1 ;;  		-R|--repackage)   REPKG=1 ;;  		--skipinteg)      SKIPINTEG=1 ;; +		--sign)           SIGNPKG='y' ;;  		--source)         SOURCEONLY=1 ;;  		-s|--syncdeps)    DEP_BIN=1 ;; @@ -1685,6 +1808,9 @@ done  [[ -n ${PKGDEST} ]] && _PKGDEST=$(canonicalize_path ${PKGDEST})  [[ -n ${SRCDEST} ]] && _SRCDEST=$(canonicalize_path ${SRCDEST})  [[ -n ${SRCPKGDEST} ]] && _SRCPKGDEST=$(canonicalize_path ${SRCPKGDEST}) +[[ -n ${PKGEXT} ]] && _PKGEXT=${PKGEXT} +[[ -n ${SRCEXT} ]] && _SRCEXT=${SRCEXT} +[[ -n ${GPGKEY} ]] && _GPGKEY=${GPGKEY}  # default config is makepkg.conf  MAKEPKG_CONF=${MAKEPKG_CONF:-$confdir/makepkg.conf} @@ -1748,6 +1874,9 @@ fi  SRCPKGDEST=${_SRCPKGDEST:-$SRCPKGDEST}  SRCPKGDEST=${SRCPKGDEST:-$startdir} #default to $startdir if undefined +PKGEXT=${_PKGEXT:-$PKGEXT} +SRCEXT=${_SRCEXT:-$SRCEXT} +GPGKEY=${_GPGKEY:-$GPGKEY}  if (( HOLDVER )) && [[ -n $FORCE_VER ]]; then  	# The '\\0' is here to prevent gettext from thinking --holdver is an option @@ -1899,6 +2028,22 @@ if [[ -n "${PKGLIST[@]}" ]]; then  	pkgname=("${PKGLIST[@]}")  fi +# check if gpg signature is to be created and if signing key is valid +if [[ -z "$SIGNPKG" && $(check_buildenv sign) == 'y' ]]; then +  SIGNPKG='y' +fi +if [[ $SIGNPKG == 'y' ]]; then +	if ! gpg --list-key ${GPGKEY} &>/dev/null; then +		if [[ ! -z $GPGKEY ]]; then +			error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")" +		else +			error "$(gettext "There is no key in your keyring.")" +		fi +		exit 1 +	fi +fi + +  if (( ! SPLITPKG )); then  	fullver=$(get_full_version $epoch $pkgver $pkgrel)  	if [[ -f $PKGDEST/${pkgname}-${fullver}-${CARCH}${PKGEXT} \ diff --git a/scripts/pacman-key.sh.in b/scripts/pacman-key.sh.in new file mode 100644 index 00000000..20ec20fa --- /dev/null +++ b/scripts/pacman-key.sh.in @@ -0,0 +1,332 @@ +#!@BASH_SHELL@ -e +# +#   pacman-key - manages pacman's keyring +#                Based on apt-key, from Debian +#   @configure_input@ +# +#   Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org> +# +#   This program is free software; you can redistribute it and/or modify +#   it under the terms of the GNU General Public License as published by +#   the Free Software Foundation; either version 2 of the License, or +#   (at your option) any later version. +# +#   This program is distributed in the hope that it will be useful, +#   but WITHOUT ANY WARRANTY; without even the implied warranty of +#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#   GNU General Public License for more details. +# +#   You should have received a copy of the GNU General Public License +#   along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +# gettext initialization +export TEXTDOMAIN='pacman' +export TEXTDOMAINDIR='@localedir@' + +myver="@PACKAGE_VERSION@" + +msg() { +	local mesg=$1; shift +	printf "==> ${mesg}\n" "$@" >&1 +} + +msg2() { +	(( QUIET )) && return +	local mesg=$1; shift +	printf "  -> ${mesg}\n" "$@" >&1 +} + +warning() { +	local mesg=$1; shift +	printf "==> $(gettext "WARNING:") ${mesg}\n" "$@" >&2 +} + +error() { +	local mesg=$1; shift +	printf "==> $(gettext "ERROR:") ${mesg}\n" "$@" >&2 +} + +usage() { +	printf "pacman-key (pacman) %s\n" ${myver} +	echo +	printf "$(gettext "Usage: %s [options] <command> [arguments]")\n" $(basename $0) +	echo +	echo "$(gettext "Manage pacman's list of trusted keys")" +	echo +	echo "$(gettext "Options must be placed before commands. The available options are:")" +	printf "$(gettext "  --config <file>  Use an alternate config file (instead of '%s')")\n" "$CONFIG" +	printf "$(gettext "  --gpgdir         Set an alternate directory for gnupg (instead of '%s')")\n" "$PACMAN_KEYRING_DIR" +	echo +	echo "$(gettext "The available commands are:")" +	echo "$(gettext "  -a, --add [<file(s)>]     Add the specified keys (empty for stdin)")" +	echo "$(gettext "  -d, --del <keyid(s)>      Remove the specified keyids")" +	echo "$(gettext "  -e, --export <keyid(s)>   Export the specified keyids")" +	echo "$(gettext "  -f, --finger [<keyid(s)>] List fingerprint for specified or all keyids")" +	echo "$(gettext "  -h, --help                Show this help message and exit")" +	echo "$(gettext "  -l, --list                List keys")" +	echo "$(gettext "  -r, --receive <keyserver> <keyid(s)> Fetch the specified keyids")" +	echo "$(gettext "  -t, --trust <keyid(s)>    Set the trust level of the given keyids")" +	echo "$(gettext "  -u, --updatedb            Update the trustdb of pacman")" +	echo "$(gettext "  -V, --version             Show program version")" +	echo "$(gettext "  --adv <params>            Use pacman's keyring with advanced gpg commands")" +	printf "$(gettext "  --reload                  Reload the default keys")" +	echo +} + +version() { +	printf "pacman-key (pacman) %s\n" "${myver}" +	printf "$(gettext "\ +Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org>.\n\ +This is free software; see the source for copying conditions.\n\ +There is NO WARRANTY, to the extent permitted by law.\n")" +} + +# Read provided file and search for values matching the given key +# The contents of the file are expected to be in this format: key = value +# 'key', 'equal sign' and 'value' can be surrounded by random whitespace +# Usage: get_from "$file" "$key" # returns the value for the first matching key in the file +get_from() { +	while read key _ value; do +		if [[ $key = $2 ]]; then +			echo "$value" +			break +		fi +	done < "$1" +} + +reload_keyring() { +	local PACMAN_SHARE_DIR='@prefix@/share/pacman' +	local GPG_NOKEYRING="gpg --batch --quiet --ignore-time-conflict --no-options --no-default-keyring --homedir ${PACMAN_KEYRING_DIR}" + +	# Variable used for iterating on keyrings +	local key +	local key_id + +	# Keyring with keys to be added to the keyring +	local ADDED_KEYS="${PACMAN_SHARE_DIR}/addedkeys.gpg" + +	# Keyring with keys that were deprecated and will eventually be deleted +	local DEPRECATED_KEYS="${PACMAN_SHARE_DIR}/deprecatedkeys.gpg" + +	# List of keys removed from the keyring. This file is not a keyring, unlike the others. +	# It is a textual list of values that gpg recogniezes as identifiers for keys. +	local REMOVED_KEYS="${PACMAN_SHARE_DIR}/removedkeys" + +	# Verify signatures of related files, if they exist +	if [[ -r "${ADDED_KEYS}" ]]; then +		msg "$(gettext "Verifying official keys file signature...")" +		if ! ${GPG_PACMAN} --quiet --batch --verify "${ADDED_KEYS}.sig" 1>/dev/null; then +			error "$(gettext "The signature of file %s is not valid.")" "${ADDED_KEYS}" +			exit 1 +		fi +	fi + +	if [[ -r "${DEPRECATED_KEYS}" ]]; then +		msg "$(gettext "Verifying deprecated keys file signature...")" +		if ! ${GPG_PACMAN} --quiet --batch --verify "${DEPRECATED_KEYS}.sig" 1>/dev/null; then +			error "$(gettext "The signature of file %s is not valid.")" "${DEPRECATED_KEYS}" +			exit 1 +		fi +	fi + +	if [[ -r "${REMOVED_KEYS}" ]]; then +		msg "$(gettext "Verifying deleted keys file signature...")" +		if ! ${GPG_PACMAN} --quiet --batch --verify "${REMOVED_KEYS}.sig"; then +			error "$(gettext "The signature of file %s is not valid.")" "${REMOVED_KEYS}" +			exit 1 +		fi +	fi + +	# Read the key ids to an array. The conversion from whatever is inside the file +	# to key ids is important, because key ids are the only guarantee of identification +	# for the keys. +	local -A removed_ids +	if [[ -r "${REMOVED_KEYS}" ]]; then +		while read key; do +			local key_values name +			key_values=$(${GPG_PACMAN} --quiet --with-colons --list-key "${key}" | grep ^pub | cut -d: -f5,10 --output-delimiter=' ') +			if [[ -n $key_values ]]; then +				# The first word is the key_id +				key_id=${key_values%% *} +				# the rest if the name of the owner +				name=${key_values#* } +				if [[ -n ${key_id} ]]; then +					# Mark this key to be deleted +					removed_ids[$key_id]="$name" +				fi +			fi +		done < "${REMOVED_KEYS}" +	fi + +	# List of keys that must be kept installed, even if in the list of keys to be removed +	local HOLD_KEYS=$(get_from "$CONFIG" "HoldKeys") + +	# Remove the keys that must be kept from the set of keys that should be removed +	if [[ -n ${HOLD_KEYS} ]]; then +		for key in ${HOLD_KEYS}; do +			key_id=$(${GPG_PACMAN} --quiet --with-colons --list-key "${key}" | grep ^pub | cut -d: -f5) +			if [[ -n "${removed_ids[$key_id]}" ]]; then +				unset removed_ids[$key_id] +			fi +		done +	fi + +	# Add keys from the current set of keys from pacman-keyring package. The web of trust will +	# be updated automatically. +	if [[ -r "${ADDED_KEYS}" ]]; then +		msg "$(gettext "Appending official keys...")" +		local add_keys=$(${GPG_NOKEYRING} --keyring "${ADDED_KEYS}" --with-colons --list-keys | grep ^pub | cut -d: -f5) +		for key_id in ${add_keys}; do +			# There is no point in adding a key that will be deleted right after +			if [[ -z "${removed_ids[$key_id]}" ]]; then +				${GPG_NOKEYRING} --keyring "${ADDED_KEYS}" --export "${key_id}" | ${GPG_PACMAN} --import +			fi +		done +	fi + +	if [[ -r "${DEPRECATED_KEYS}" ]]; then +		msg "$(gettext "Appending deprecated keys...")" +		local add_keys=$(${GPG_NOKEYRING} --keyring "${DEPRECATED_KEYS}" --with-colons --list-keys | grep ^pub | cut -d: -f5) +		for key_id in ${add_keys}; do +			# There is no point in adding a key that will be deleted right after +			if [[ -z "${removed_ids[$key_id]}" ]]; then +				${GPG_NOKEYRING} --keyring "${DEPRECATED_KEYS}" --export "${key_id}" | ${GPG_PACMAN} --import +			fi +		done +	fi + +	# Remove the keys not marked to keep +	if (( ${#removed_ids[@]} > 0 )); then +		msg "$(gettext "Removing deleted keys from keyring...")" +		for key_id in "${!removed_ids[@]}"; do +			echo "  removing key $key_id - ${removed_ids[$key_id]}" +			${GPG_PACMAN} --quiet --batch --yes --delete-key "${key_id}" +		done +	fi + +	# Update trustdb, just to be sure +	msg "$(gettext "Updating trust database...")" +	${GPG_PACMAN} --batch --check-trustdb +} + +# PROGRAM START +if ! type gettext &>/dev/null; then +	gettext() { +		echo "$@" +	} +fi + +if [[ $1 != "--version" && $1 != "-V" && $1 != "--help" && $1 != "-h" && $1 != "" ]]; then +	if type -p gpg >/dev/null 2>&1 = 1; then +		error "$(gettext "gnupg does not seem to be installed.")" +		msg2 "$(gettext "pacman-key requires gnupg for most operations.")" +		exit 1 +	elif (( EUID != 0 )); then +		error "$(gettext "pacman-key needs to be run as root.")" +		exit 1 +	fi +fi + +# Parse global options +CONFIG="@sysconfdir@/pacman.conf" +PACMAN_KEYRING_DIR="@sysconfdir@/pacman.d/gnupg" +while [[ $1 =~ ^--(config|gpgdir)$ ]]; do +	case "$1" in +		--config) shift; CONFIG="$1" ;; +		--gpgdir) shift; PACMAN_KEYRING_DIR="$1" ;; +	esac +	shift +done + +if [[ ! -r "${CONFIG}" ]]; then +	error "$(gettext "%s not found.")" "$CONFIG" +	exit 1 +fi + +# Read GPGDIR from $CONFIG. +if [[ GPGDIR=$(get_from "$CONFIG" "GPGDir") == 0 ]]; then +	PACMAN_KEYRING_DIR="${GPGDIR}" +fi +GPG_PACMAN="gpg --homedir ${PACMAN_KEYRING_DIR} --no-permission-warning" + +# Try to create $PACMAN_KEYRING_DIR if non-existent +# Check for simple existence rather than for a directory as someone may want +# to use a symlink here +[[ -e ${PACMAN_KEYRING_DIR} ]] || mkdir -p -m 755 "${PACMAN_KEYRING_DIR}" + +# Parse and execute command +command="$1" +if [[ -z "${command}" ]]; then +	usage +	exit 1 +fi +shift + +case "${command}" in +	-a|--add) +		# If there is no extra parameter, gpg will read stdin +		${GPG_PACMAN} --quiet --batch --import "$@" +		;; +	-d|--del) +		if (( $# == 0 )); then +			error "$(gettext "You need to specify at least one key identifier")" +			exit 1 +		fi +		${GPG_PACMAN} --quiet --batch --delete-key --yes "$@" +		;; +	-u|--updatedb) +		${GPG_PACMAN} --batch --check-trustdb +		;; +	--reload) +		reload_keyring +		;; +	-l|--list) +		${GPG_PACMAN} --batch --list-sigs "$@" +		;; +	-f|--finger) +		${GPG_PACMAN} --batch --fingerprint "$@" +		;; +	-e|--export) +		${GPG_PACMAN} --armor --export "$@" +		;; +	-r|--receive) +		if (( $# < 2 )); then +			error "$(gettext "You need to specify the keyserver and at least one key identifier")" +			exit 1 +		fi +		keyserver="$1" +		shift +		${GPG_PACMAN} --keyserver "${keyserver}" --recv-keys "$@" +		;; +	-t|--trust) +		if (( $# == 0 )); then +			error "$(gettext "You need to specify at least one key identifier")" +			exit 1 +		fi +		while (( $# > 0 )); do +			# Verify if the key exists in pacman's keyring +			if ${GPG_PACMAN} --list-keys "$1" > /dev/null 2>&1; then +				${GPG_PACMAN} --edit-key "$1" +			else +				error "$(gettext "The key identified by %s doesn't exist")" "$1" +				exit 1 +			fi +			shift +		done +		;; +	--adv) +		msg "$(gettext "Executing: %s ")$*" "${GPG_PACMAN}" +		${GPG_PACMAN} "$@" || ret=$? +		exit $ret +		;; +	-h|--help) +		usage; exit 0 ;; +	-V|--version) +		version; exit 0 ;; +	*) +		error "$(gettext "Unknown command:") $command" +		usage; exit 1 ;; +esac + +# vim: set ts=2 sw=2 noet: diff --git a/scripts/pkgdelta.sh.in b/scripts/pkgdelta.sh.in index ecca4277..d17b41d2 100644 --- a/scripts/pkgdelta.sh.in +++ b/scripts/pkgdelta.sh.in @@ -163,3 +163,5 @@ if ! type xdelta3 &>/dev/null; then  fi  create_xdelta "$1" "$2" + +# vim: set ts=2 sw=2 noet: diff --git a/scripts/repo-add.sh.in b/scripts/repo-add.sh.in index 02ab389c..0ffc0df5 100644 --- a/scripts/repo-add.sh.in +++ b/scripts/repo-add.sh.in @@ -4,8 +4,7 @@  #   repo-remove - remove a package entry from a given repo database file  #   @configure_input@  # -#   Copyright (c) 2006-2008 Aaron Griffin <aaron@archlinux.org> -#   Copyright (c) 2007-2008 Dan McGee <dan@archlinux.org> +#   Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>  #  #   This program is free software; you can redistribute it and/or modify  #   it under the terms of the GNU General Public License as published by @@ -30,6 +29,8 @@ confdir='@sysconfdir@'  QUIET=0  DELTA=0  WITHFILES=0 +SIGN=0 +VERIFY=0  REPO_DB_FILE=  LOCKFILE=  CLEAN_LOCK=0 @@ -61,34 +62,42 @@ error() {  # print usage instructions  usage() { -	printf "repo-add, repo-remove (pacman) %s\n\n" "$myver" -	printf "$(gettext "Usage: repo-add [-d] [-f] [-q] <path-to-db> <package|delta> ...\n")" -	printf "$(gettext "Usage: repo-remove [-q] <path-to-db> <packagename|delta> ...\n\n")" -	printf "$(gettext "\ +	cmd="$(basename $0)" +	printf "%s (pacman) %s\n\n" "$cmd" "$myver" +	if [[ $cmd == "repo-add" ]] ; then +		printf "$(gettext "Usage: repo-add [options] <path-to-db> <package|delta> ...\n")" +		printf "$(gettext "\  repo-add will update a package database by reading a package file.\n\  Multiple packages to add can be specified on the command line.\n\n")" -	printf "$(gettext "\ +		printf "$(gettext "Options:\n")" +		printf "$(gettext "  -d, --delta       generate and add delta for package update\n")" +		printf "$(gettext "  -f, --files       update database's file list\n")" +	elif [[ $cmd == "repo-remove" ]] ; then +		printf "$(gettext "Usage: repo-remove [options] <path-to-db> <packagename|delta> ...\n\n")" +		printf "$(gettext "\  repo-remove will update a package database by removing the package name\n\  specified on the command line from the given repo database. Multiple\n\  packages to remove can be specified on the command line.\n\n")" -	printf "$(gettext "\ -Use the -q/--quiet flag to minimize output to basic messages, warnings,\n\ -and errors.\n\n")" -	printf "$(gettext "\ -Use the -d/--delta flag to automatically generate and add a delta file\n\ -between the old entry and the new one, if the old package file is found\n\ -next to the new one.\n\n")" -	printf "$(gettext "\ -Use the -f/--files flag to update a database including file entries.\n\n")" -	echo "$(gettext "Example:  repo-add /path/to/repo.db.tar.gz pacman-3.0.0.pkg.tar.gz")" -	echo "$(gettext "Example:  repo-remove /path/to/repo.db.tar.gz kernel26")" +		printf "$(gettext "Options:\n")" +	fi +	printf "$(gettext "  -q, --quiet       minimize output\n")" +	printf "$(gettext "  -s, --sign        sign database with GnuPG after update\n")" +	printf "$(gettext "  -k, --key <key>   use the specified key to sign the database\n")" +	printf "$(gettext "  -v, --verify      verify database's signature before update\n")" +	printf "$(gettext "\n\ +See %s(8) for more details and descriptions of the available options.\n\n")" $cmd +	if [[ $cmd == "repo-add" ]] ; then +		echo "$(gettext "Example:  repo-add /path/to/repo.db.tar.gz pacman-3.0.0-1-i686.pkg.tar.gz")" +	elif [[ $cmd == "repo-remove" ]] ; then +		echo "$(gettext "Example:  repo-remove /path/to/repo.db.tar.gz kernel26")" +	fi  }  version() { -	printf "repo-add, repo-remove (pacman) %s\n\n" "$myver" +	cmd="$(basename $0)" +	printf "%s (pacman) %s\n\n" "$cmd" "$myver"  	printf "$(gettext "\ -Copyright (C) 2006-2008 Aaron Griffin <aaron@archlinux.org>.\n\ -Copyright (c) 2007-2008 Dan McGee <dan@archlinux.org>.\n\n\ +Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>\n\n\  This is free software; see the source for copying conditions.\n\  There is NO WARRANTY, to the extent permitted by law.\n")"  } @@ -184,14 +193,62 @@ db_remove_delta()  	return 1  } # end db_remove_delta +check_gpg() { +	if ! type -p gpg >/dev/null; then +		error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")" +		exit 1 # $E_MISSING_PROGRAM +	fi +} + +# sign the package database once repackaged +create_signature() { +	(( ! SIGN )) && return +	local dbfile="$1" +	local ret=0 +	msg "$(gettext "Signing database...")" + +	local SIGNWITHKEY="" +	if [[ -n $GPGKEY ]]; then +		SIGNWITHKEY="-u ${GPGKEY}" +	fi +	gpg --detach-sign --use-agent ${SIGNWITHKEY} "$dbfile" &>/dev/null || ret=$? + +	if (( ! ret )); then +		msg2 "$(gettext "Created signature file %s.")" "$dbfile.sig" +	else +		warning "$(gettext "Failed to sign package database.")" +	fi +} + +# verify the existing package database signature +verify_signature() { +	(( ! VERIFY )) && return +	local dbfile="$1" +	local ret=0 +	msg "$(gettext "Verifying database signature...")" + +	if [[ ! -f $dbfile.sig ]]; then +		warning "$(gettext "No existing signature found, skipping verification.")" +		return +	fi +	gpg --verify "$dbfile.sig" || ret=$? +	if (( ! ret )); then +		msg2 "$(gettext "Database signature file verified.")" +	else +		error "$(gettext "Database signature was NOT valid!")" +		exit 1 +	fi +} +  # write an entry to the pacman database  #   arg1 - path to package  db_write_entry()  {  	# blank out all variables  	local pkgfile="$1" -	local pkgname pkgver pkgdesc csize size md5sum url arch builddate packager \ -		_groups _licenses _replaces _depends _conflicts _provides _optdepends +	local pkgname pkgver pkgdesc csize size url arch builddate packager \ +		_groups _licenses _replaces _depends _conflicts _provides _optdepends \ +		md5sum sha256sum pgpsig  	local OLDIFS="$IFS"  	# IFS (field separator) is only the newline character @@ -219,10 +276,19 @@ db_write_entry()  	IFS=$OLDIFS -	# get md5sum and compressed size of package +	csize=$(@SIZECMD@ "$pkgfile") + +	# compute checksums +	msg2 "$(gettext "Computing checksums...")"  	md5sum="$(openssl dgst -md5 "$pkgfile")"  	md5sum="${md5sum##* }" -	csize=$(@SIZECMD@ "$pkgfile") +	sha256sum="$(openssl dgst -sha256 "$pkgfile")" +	sha256sum="${sha256sum##* }" + +	# compute base64'd PGP signature +	if [[ -f "$pkgfile.sig" ]]; then +		pgpsig=$(openssl base64 -in "$pkgfile.sig" | tr -d '\n') +	fi  	# ensure $pkgname and $pkgver variables were found  	if [[ -z $pkgname || -z $pkgver ]]; then @@ -264,9 +330,12 @@ db_write_entry()  	[[ -n $csize ]] && echo -e "%CSIZE%\n$csize\n" >>desc  	[[ -n $size ]] && echo -e "%ISIZE%\n$size\n" >>desc -	# compute checksums -	msg2 "$(gettext "Computing md5 checksums...")" +	# add checksums  	echo -e "%MD5SUM%\n$md5sum\n" >>desc +	echo -e "%SHA256SUM%\n$sha256sum\n" >>desc + +	# add PGP sig +	[[ -n $pgpsig ]] && echo -e "%PGPSIG%\n$pgpsig\n" >>desc  	[[ -n $url ]] && echo -e "%URL%\n$url\n" >>desc  	write_list_entry "LICENSE" "$_licenses" "desc" @@ -352,6 +421,7 @@ check_repo_db()  				exit 1  			fi  		fi +		verify_signature "$REPO_DB_FILE"  		msg "$(gettext "Extracting database to a temporary location...")"  		bsdtar -xf "$REPO_DB_FILE" -C "$tmpdir"  	else @@ -477,24 +547,50 @@ trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR  success=0  # parse arguments -for arg in "$@"; do -	case "$arg" in +while [[ $# > 0 ]]; do +	case "$1" in  		-q|--quiet) QUIET=1;;  		-d|--delta) DELTA=1;;  		-f|--files) WITHFILES=1;; +		-s|--sign) +			check_gpg +			SIGN=1 +			if ! gpg --list-key ${GPGKEY} &>/dev/null; then +				if [[ ! -z $GPGKEY ]]; then +					error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")" +				else +					error "$(gettext "There is no key in your keyring.")" +				fi +				exit 1 +			fi +			;; +		-k|--key) +			check_gpg +			shift +			GPGKEY="$1" +			if ! gpg --list-key ${GPGKEY} &>/dev/null; then +				error "$(gettext "The key ${GPGKEY} does not exist in your keyring.")" +				exit 1 +			fi +			;; +		-v|--verify) +			check_gpg +			VERIFY=1 +			;;  		*)  			if [[ -z $REPO_DB_FILE ]]; then -				REPO_DB_FILE="$arg" +				REPO_DB_FILE="$1"  				LOCKFILE="$REPO_DB_FILE.lck"  				check_repo_db  			else  				case "$cmd" in -					repo-add) add $arg && success=1 ;; -					repo-remove) remove $arg && success=1 ;; +					repo-add) add $1 && success=1 ;; +					repo-remove) remove $1 && success=1 ;;  				esac  			fi  			;;  	esac +	shift  done  # if at least one operation was a success, re-zip database @@ -519,15 +615,24 @@ if (( success )); then  		warning "$(gettext "No packages remain, creating empty database.")"  		bsdtar -c${TAR_OPT}f "$filename" -T /dev/null  	fi +	create_signature "$filename" +  	popd >/dev/null  	[[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old" +	[[ -f $REPO_DB_FILE.sig ]] && rm -f "$REPO_DB_FILE.sig"  	[[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE" +	[[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"  	dblink="${REPO_DB_FILE%.tar.*}"  	target=${REPO_DB_FILE##*/}  	ln -sf "$target" "$dblink" 2>/dev/null || \  		ln -f "$target" "$dblink" 2>/dev/null || \  		cp "$REPO_DB_FILE" "$dblink" +	if [[ -f "$target.sig" ]]; then +		ln -sf "$target.sig" "$dblink.sig" 2>/dev/null || \ +			ln -f "$target.sig" "$dblink.sig" 2>/dev/null || \ +			cp "$REPO_DB_FILE.sig" "$dblink.sig" +	fi  else  	msg "$(gettext "No packages modified, nothing to do.")"  	exit 1 | 
