2 # No way I try to deal with a crippled sh just for POSIX foo.
4 # Copyright (C) 2011,2012 Joerg Jaspert <joerg@debian.org>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License as
8 # published by the Free Software Foundation; version 2.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 # make sure to only use defined variables
24 # ERR traps should be inherited from functions too.
27 # import the general variable set.
28 export SCRIPTVARS=/srv/ftp-master.debian.org/dak/config/debian/vars
33 # And use one locale, no matter what the caller has set
36 PROGRAM="buildd-add-keys"
38 # common functions are "outsourced"
39 . "${configdir}/common"
43 trap - ERR EXIT TERM HUP INT QUIT
45 for TEMPFILE in GPGSTATUS GPGLOGS GPGOUTF TEMPKEYDATA; do
47 if [ -n "${DELF}" ] && [ -f "${DELF}" ]; then
54 base="${base}/scripts/builddkeyrings"
55 INCOMING="${base}/incoming"
56 ERRORS="${base}/errors"
57 ADMINS="${base}/adminkeys.gpg"
58 ARCHADMINS="${base}/archadminkeys"
59 STAMPFILE="${base}/updatedkeyring"
61 # Default options for our gpg calls
62 DEFGPGOPT="--no-default-keyring --batch --no-tty --no-options --exit-on-status-write-error --no-greeting"
64 if ! [ -d "${INCOMING}" ]; then
65 log "Missing incoming dir, nothing to do"
70 KEYS=$(find . -maxdepth 1 -mindepth 1 -type f -name \*.key | sed -e "s,./,," | xargs)
71 if [ -z "${KEYS}" ]; then
75 trap cleanup ERR EXIT TERM HUP INT QUIT
77 # Tell prepare-dir that there is an update and it can run
80 # Whenever something goes wrong, its put in there.
83 # We process all new files in our incoming directory
84 for file in ${KEYS}; do
86 # First we want to see if we recognize the filename. The buildd people have
87 # to follow a certain schema:
88 # architecture_builddname.YEAR-MONTH-DAY_HOURMINUTE.key
89 if [[ $file =~ (.*)_(.*).([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}[0-9]{2}).key ]]; then
90 ARCH=${BASH_REMATCH[1]}
91 BUILDD=${BASH_REMATCH[2]}
92 # Right now timestamp is unused
93 TIMESTAMP=${BASH_REMATCH[3]}
95 log "Unknown file ${file}, not processing"
96 mv "${INCOMING}/${file}" "${ERRORS}/unknown.${file}.$(date -Is)"
100 # Do we know the architecture?
102 for carch in ${archs}; do
103 if [ "${ARCH}" == "${carch}" ]; then
104 log "Known arch ${ARCH}, buildd ${BUILDD}"
110 if [ ${found} -eq 0 ]; then
111 log "Unknown architecture ${ARCH}"
112 mv "${INCOMING}/${file}" "${ERRORS}/unknownarch.${file}.$(date -Is)"
116 # If we did have a file with this name already somethings wrong
117 if [ -f "${base}/${ARCH}/${file}" ]; then
118 log "Already processed this file"
119 mv "${INCOMING}/${file}" "${ERRORS}/duplicate.${file}.$(date -Is)"
123 # Where we want the status-fd from gpgv turn up
124 GPGSTATUS=$(mktemp -p "${TMPDIR}" GPGSTATUS.XXXXXX)
125 # Same for the loggger-fd
126 GPGLOGS=$(mktemp -p "${TMPDIR}" GPGLOGS.XXXXXX)
127 # And "decrypt" gives us output, the key without the pgp sig around it
128 GPGOUTF=$(mktemp -p "${TMPDIR}" GPGOUTF.XXXXXX)
130 # Open the filehandles, assigning them to the two files, so we can let gpg use them
131 exec 4> "${GPGSTATUS}"
134 KEYRINGS="--keyring ${ADMINS}"
135 if [ -f "${ARCHADMINS}/${ARCH}.gpg" ]; then
136 KEYRINGS="${KEYRINGS} --keyring ${ARCHADMINS}/${ARCH}.gpg"
138 # So lets run gpg, status/logger into the two files, to "decrypt" the keyfile
139 if ! gpg ${DEFGPGOPT} ${KEYRINGS} --status-fd 4 --logger-fd 5 --decrypt "${INCOMING}/${file}" > "${GPGOUTF}"; then
141 log "gpg returned with ${ret}, not adding key from file ${file}"
143 mv "${INCOMING}/${file}" "${ERRORS}/gpgerror.${file}.${DATE}"
144 mv "${GPGSTATUS}" "${ERRORS}/gpgerror.${file}.gpgstatus.${DATE}"
145 mv "${GPGLOGS}" "${ERRORS}/gpgerror.${file}.gpglogs.${DATE}"
150 # Read in the status output
151 GPGSTAT=$(cat "${GPGSTATUS}")
152 # And check if we like the sig. It has to be both, GOODISG and VALIDSIG or we don't accept it
153 if [[ ${GPGSTAT} =~ "GOODSIG" ]] && [[ ${GPGSTAT} =~ "VALIDSIG" ]]; then
154 log "Signature for ${file} accepted"
156 log "We are missing one of GOODSIG or VALIDSIG"
158 mv "${INCOMING}/${file}" "${ERRORS}/badsig.${file}.${DATE}"
159 mv "${GPGSTATUS}" "${ERRORS}/badsig.${file}.gpgstatus.${DATE}"
160 mv "${GPGLOGS}" "${ERRORS}/badsig.${file}.gpglogs.${DATE}"
165 # So at this point we know we accepted the signature of the file as valid,
166 # that is it is from a key allowed for this architecture. Which only
167 # leaves us with the task of checking if the key fulfills the requirements
168 # before we add it to the architectures keyring.
170 # Those currently are:
171 # - keysize 4096 or larger
172 # - RSA key, no encryption capability
173 # - UID matching "buildd autosigning key BUILDDNAME <buildd_ARCH-BUILDDNAME@buildd.debian.org>
174 # - expire within a 360 days
175 # - maximum 2 keys per architecture and buildd
177 TEMPKEYDATA=$(mktemp -p "${TMPDIR}" BDKEYS.XXXXXX)
179 # We also need to ensure this works, otherwise manually mangled files can break us here
180 if ! gpg ${DEFGPGOPT} --with-colons "${GPGOUTF}" > "${TEMPKEYDATA}"; then
181 log "For some reason we could validate the sig but failed on getting key details"
183 mv "${INCOMING}/${file}" "${ERRORS}/badsig.${file}.${DATE}"
184 mv "${GPGSTATUS}" "${ERRORS}/badsig.${file}.gpgstatus.${DATE}"
185 mv "${GPGLOGS}" "${ERRORS}/badsig.${file}.gpglogs.${DATE}"
187 rm -f "${TMPKEYDATA}"
191 # Read in the TEMPKEYDATAFILE, but avoid using a subshell like a
192 # while read line otherwise would do
193 exec 4<> "${TEMPKEYDATA}"
195 #pub:-:4096:1:FAB983612A6554FA:2011-03-24:2011-07-22::-:buildd autosigning key poulenc <buildd_powerpc-poulenc@buildd.debian.org>:
197 # Of course this sucky gpg crapshit of an "interface" does give you different things depending on how people
198 # created their keys. And of course the buildd people created the test keys differently to what they now do
199 # which just means extra work for nothing. So as they now do other steps, the thing we get back suddenly looks like
201 #pub:-:4096:1:99595DC7865BEAD2:2011-03-26:2011-07-24::-:
202 #uid:::::::::buildd autosigning key corelli <buildd_mips-corelli@buildd.debian.org>:
204 # Besides fiddling out the data we need to check later, this regex also check:
205 # - the keytype (:1:, 1 there means RSA)
207 # - that the key does have an expiration date (or it wont match, the second date
208 # field would be empty
209 regex="^pub:-:([0-9]{4}):1:([0-9A-F]{16}):([0-9]{4}-[0-9]{2}-[0-9]{2}):([0-9]{4}-[0-9]{2}-[0-9]{2})::-:(buildd autosigning key ${BUILDD} <buildd_${ARCH}-${BUILDD}@buildd.debian.org>):$"
210 regex2="^pub:-:([0-9]{4}):1:([0-9A-F]{16}):([0-9]{4}-[0-9]{2}-[0-9]{2}):([0-9]{4}-[0-9]{2}-[0-9]{2})::-:$"
211 regex3="^uid:::::::::(buildd autosigning key ${BUILDD} <buildd_${ARCH}-${BUILDD}@buildd.debian.org>):$"
212 while read line <&4; do
213 if [[ $line =~ $regex ]]; then
214 KEYSIZE=${BASH_REMATCH[1]}
215 KEYID=${BASH_REMATCH[2]}
216 KEYCREATE=${BASH_REMATCH[3]}
217 KEYEXPIRE=${BASH_REMATCH[4]}
218 KEYUID=${BASH_REMATCH[5]}
219 elif [[ $line =~ $regex2 ]]; then
220 KEYSIZE=${BASH_REMATCH[1]}
221 KEYID=${BASH_REMATCH[2]}
222 KEYCREATE=${BASH_REMATCH[3]}
223 KEYEXPIRE=${BASH_REMATCH[4]}
224 elif [[ $line =~ $regex3 ]]; then
225 KEYUID=${BASH_REMATCH[1]}
227 log "Didn't recognize the key. Go kiss gpg"
229 mv "${INCOMING}/${file}" "${ERRORS}/badkey.${file}.${DATE}"
230 mv "${GPGSTATUS}" "${ERRORS}/badkey.${file}.gpgstatus.${DATE}"
231 mv "${GPGLOGS}" "${ERRORS}/badkey.${file}.gpglogs.${DATE}"
236 if [ -z "${KEYUID}" ]; then
237 log "Did not recognize the UID format"
239 mv "${INCOMING}/${file}" "${ERRORS}/keyuid.${file}.${DATE}"
240 mv "${GPGSTATUS}" "${ERRORS}/keyuid.${file}.gpgstatus.${DATE}"
241 mv "${GPGLOGS}" "${ERRORS}/keyuid.${file}.gpglogs.${DATE}"
245 # We do want 4096 or anything above
246 if [ ${KEYSIZE} -lt 4096 ]; then
247 log "Keysize ${KEYSIZE} too small"
249 mv "${INCOMING}/${file}" "${ERRORS}/keysize.${file}.${DATE}"
250 mv "${GPGSTATUS}" "${ERRORS}/keysize.${file}.gpgstatus.${DATE}"
251 mv "${GPGLOGS}" "${ERRORS}/keysize.${file}.gpglogs.${DATE}"
256 # We want a maximum lifetime of 365 days, so check that.
257 # Easiest to compare in epoch, so lets see, 365 days midnight from now,
258 # compared with their set expiration date at midnight
259 # maxdate should turn out higher. just in case we make it 366 for this check
260 maxdate=$(date -d '366 day 00:00:00' +%s)
261 theirexpire=$(date -d "${KEYEXPIRE} 00:00:00" +%s)
262 if [ ${theirexpire} -gt ${maxdate} ]; then
263 log "Key expiry ${KEYEXPIRE} wrong"
265 mv "${INCOMING}/${file}" "${ERRORS}/keyexpire.${file}.${DATE}"
266 mv "${GPGSTATUS}" "${ERRORS}/keyexpire.${file}.gpgstatus.${DATE}"
267 mv "${GPGLOGS}" "${ERRORS}/keyexpire.${file}.gpglogs.${DATE}"
272 # And now lets check how many keys this buildd already has. 2 is the maximum, so key
273 # rollover works. 3 won't, they have to rm one first
274 # We need to check for the amount of keys
275 ARCHKEYRING="${base}/${ARCH}/keyring.gpg"
277 KEYNO=$(gpg ${DEFGPGOPT} --keyring "${ARCHKEYRING}" --with-colons --list-keys "buildd_${ARCH}-${BUILDD}@buildd.debian.org" 2>/dev/null | grep -c '^pub:' || /bin/true )
278 if [ ${KEYNO} -gt 2 ]; then
279 log "Too many keys for ${ARCH} buildd ${BUILDD}"
281 mv "${INCOMING}/${file}" "${ERRORS}/toomany.${file}.${DATE}"
282 mv "${GPGSTATUS}" "${ERRORS}/toomany.${file}.gpgstatus.${DATE}"
283 mv "${GPGLOGS}" "${ERRORS}/toomany.${file}.gpglogs.${DATE}"
288 # Right. At this point everything should be in order, which means we should put the key into
290 KEYSUBMITTER=$(cat "${GPGSTATUS}"|grep GOODSIG)
291 KEYSUBMITTER=${KEYSUBMITTER##*GOODSIG}
292 log "${KEYSUBMITTER} added key ${KEYID} for ${ARCH} buildd ${BUILDD}, expire ${KEYEXPIRE}"
293 gpg ${DEFGPGOPT} --status-fd 4 --logger-fd 5 --keyring "${ARCHKEYRING}" --import "${GPGOUTF}" 2>/dev/null
295 mv "${INCOMING}/${file}" "${base}/${ARCH}"