Mon Mar 4 20:37:32 2024 UTC (103d)
certctl(8): Pacify formal POSIX sh syntax.

According to POSIX 2018, the syntax between `then' and `elif' and
`fi' must be a _non-empty_ list of commands:

compound_list    : linebreak term
                 | linebreak term separator
                 ;
...
if_clause        : If compound_list Then compound_list else_part Fi
                 | If compound_list Then compound_list           Fi
                 ;
else_part        : Elif compound_list Then compound_list
                 | Elif compound_list Then compound_list else_part
                 | Else compound_list
                 ;

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10_02

NetBSD's sh(1) currently doesn't enforce this and allows an empty
sequence of commands, but let's not rely on that nonstandard quirk.

Noted in PR 57997.


(riastradh)
diff -r1.6 -r1.7 src/usr.sbin/certctl/certctl.sh

cvs diff -r1.6 -r1.7 src/usr.sbin/certctl/certctl.sh (switch to unified diff)

--- src/usr.sbin/certctl/certctl.sh 2024/03/03 15:53:55 1.6
+++ src/usr.sbin/certctl/certctl.sh 2024/03/04 20:37:31 1.7
@@ -1,686 +1,687 @@ @@ -1,686 +1,687 @@
1#!/bin/sh 1#!/bin/sh
2 2
3# $NetBSD: certctl.sh,v 1.6 2024/03/03 15:53:55 riastradh Exp $ 3# $NetBSD: certctl.sh,v 1.7 2024/03/04 20:37:31 riastradh Exp $
4# 4#
5# Copyright (c) 2023 The NetBSD Foundation, Inc. 5# Copyright (c) 2023 The NetBSD Foundation, Inc.
6# All rights reserved. 6# All rights reserved.
7# 7#
8# Redistribution and use in source and binary forms, with or without 8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions 9# modification, are permitted provided that the following conditions
10# are met: 10# are met:
11# 1. Redistributions of source code must retain the above copyright 11# 1. Redistributions of source code must retain the above copyright
12# notice, this list of conditions and the following disclaimer. 12# notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright 13# 2. Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the 14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution. 15# documentation and/or other materials provided with the distribution.
16# 16#
17# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE. 27# POSSIBILITY OF SUCH DAMAGE.
28# 28#
29 29
30set -o pipefail 30set -o pipefail
31set -Ceu 31set -Ceu
32 32
33progname=${0##*/} 33progname=${0##*/}
34 34
35### Options and arguments 35### Options and arguments
36 36
37usage() 37usage()
38{ 38{
39 exec >&2 39 exec >&2
40 printf 'Usage: %s %s\n' \ 40 printf 'Usage: %s %s\n' \
41 "$progname" \ 41 "$progname" \
42 "[-nv] [-C <config>] [-c <certsdir>] [-u <untrusted>]" 42 "[-nv] [-C <config>] [-c <certsdir>] [-u <untrusted>]"
43 printf ' <cmd> <args>...\n' 43 printf ' <cmd> <args>...\n'
44 printf ' %s list\n' "$progname" 44 printf ' %s list\n' "$progname"
45 printf ' %s rehash\n' "$progname" 45 printf ' %s rehash\n' "$progname"
46 printf ' %s trust <cert>\n' "$progname" 46 printf ' %s trust <cert>\n' "$progname"
47 printf ' %s untrust <cert>\n' "$progname" 47 printf ' %s untrust <cert>\n' "$progname"
48 printf ' %s untrusted\n' "$progname" 48 printf ' %s untrusted\n' "$progname"
49 exit 1 49 exit 1
50} 50}
51 51
52certsdir=/etc/openssl/certs 52certsdir=/etc/openssl/certs
53config=/etc/openssl/certs.conf 53config=/etc/openssl/certs.conf
54distrustdir=/etc/openssl/untrusted 54distrustdir=/etc/openssl/untrusted
55nflag=false # dry run 55nflag=false # dry run
56vflag=false # verbose 56vflag=false # verbose
57 57
58# Options used by FreeBSD: 58# Options used by FreeBSD:
59# 59#
60# -D destdir 60# -D destdir
61# -M metalog 61# -M metalog
62# -U (unprivileged) 62# -U (unprivileged)
63# -d distbase 63# -d distbase
64# 64#
65while getopts C:c:nu:v f; do 65while getopts C:c:nu:v f; do
66 case $f in 66 case $f in
67 C) config=$OPTARG;; 67 C) config=$OPTARG;;
68 c) certsdir=$OPTARG;; 68 c) certsdir=$OPTARG;;
69 n) nflag=true;; 69 n) nflag=true;;
70 u) distrustdir=$OPTARG;; 70 u) distrustdir=$OPTARG;;
71 v) vflag=true;; 71 v) vflag=true;;
72 \?) usage;; 72 \?) usage;;
73 esac 73 esac
74done 74done
75shift $((OPTIND - 1)) 75shift $((OPTIND - 1))
76 76
77if [ $# -lt 1 ]; then 77if [ $# -lt 1 ]; then
78 usage 78 usage
79fi 79fi
80cmd=$1 80cmd=$1
81 81
82### Global state 82### Global state
83 83
84config_paths= 84config_paths=
85config_manual=false 85config_manual=false
86tmpfile= 86tmpfile=
87 87
88# If tmpfile is set to nonempty, clean it up on exit. 88# If tmpfile is set to nonempty, clean it up on exit.
89 89
90trap 'test -n "$tmpfile" && rm -f "$tmpfile"' EXIT HUP INT TERM 90trap 'test -n "$tmpfile" && rm -f "$tmpfile"' EXIT HUP INT TERM
91 91
92### Subroutines 92### Subroutines
93 93
94# error <msg> ... 94# error <msg> ...
95# 95#
96# Print an error message to stderr. 96# Print an error message to stderr.
97# 97#
98# Does not exit the process. 98# Does not exit the process.
99# 99#
100error() 100error()
101{ 101{
102 echo "$progname:" "$@" >&2 102 echo "$progname:" "$@" >&2
103} 103}
104 104
105# run <cmd> <args>... 105# run <cmd> <args>...
106# 106#
107# Print a command if verbose, and run it unless it's a dry run. 107# Print a command if verbose, and run it unless it's a dry run.
108# 108#
109run() 109run()
110{ 110{
111 local t q cmdline 111 local t q cmdline
112 112
113 if $vflag; then # print command if verbose 113 if $vflag; then # print command if verbose
114 for t; do 114 for t; do
115 case $t in 115 case $t in
116 ''|*[^[:alnum:]+,-./:=_@]*) 116 ''|*[^[:alnum:]+,-./:=_@]*)
117 # empty or unsafe -- quotify 117 # empty or unsafe -- quotify
118 ;; 118 ;;
119 *) 119 *)
120 # nonempty and safe-only -- no quotify 120 # nonempty and safe-only -- no quotify
121 cmdline="${cmdline:+$cmdline }$t" 121 cmdline="${cmdline:+$cmdline }$t"
122 continue 122 continue
123 ;; 123 ;;
124 esac 124 esac
125 q=$(printf '%s' "$t" | sed -e "s/'/'\\\''/g'") 125 q=$(printf '%s' "$t" | sed -e "s/'/'\\\''/g'")
126 cmdline="${cmdline:+$cmdline }'$q'" 126 cmdline="${cmdline:+$cmdline }'$q'"
127 done 127 done
128 printf '%s\n' "$cmdline" 128 printf '%s\n' "$cmdline"
129 fi 129 fi
130 if ! $nflag; then # skip command if dry run 130 if ! $nflag; then # skip command if dry run
131 "$@" 131 "$@"
132 fi 132 fi
133} 133}
134 134
135# configure 135# configure
136# 136#
137# Parse the configuration file, initializing config_*. 137# Parse the configuration file, initializing config_*.
138# 138#
139configure() 139configure()
140{ 140{
141 local lineno status formatok vconfig line contline op path vpath vop 141 local lineno status formatok vconfig line contline op path vpath vop
142 142
143 # Count line numbers, record a persistent error status to 143 # Count line numbers, record a persistent error status to
144 # return at the end, and record whether we got a format line. 144 # return at the end, and record whether we got a format line.
145 lineno=0 145 lineno=0
146 status=0 146 status=0
147 formatok=false 147 formatok=false
148 148
149 # vis the config name for terminal-safe error messages. 149 # vis the config name for terminal-safe error messages.
150 vconfig=$(printf '%s' "$config" | vis -M) 150 vconfig=$(printf '%s' "$config" | vis -M)
151 151
152 # Read and process each line of the config file. 152 # Read and process each line of the config file.
153 while read -r line; do 153 while read -r line; do
154 lineno=$((lineno + 1)) 154 lineno=$((lineno + 1))
155 155
156 # If the line ends in an odd number of backslashes, it 156 # If the line ends in an odd number of backslashes, it
157 # has a continuation line, so read on. 157 # has a continuation line, so read on.
158 while expr "$line" : '^\(\\\\\)*\\' >/dev/null || 158 while expr "$line" : '^\(\\\\\)*\\' >/dev/null ||
159 expr "$line" : '^.*[^\\]\(\\\\\)*\\$' >/dev/null; do 159 expr "$line" : '^.*[^\\]\(\\\\\)*\\$' >/dev/null; do
160 if ! read -r contline; then 160 if ! read -r contline; then
161 error "$vconfig:$lineno: premature end of file" 161 error "$vconfig:$lineno: premature end of file"
162 return 1 162 return 1
163 fi 163 fi
164 line="$line$contline" 164 line="$line$contline"
165 done 165 done
166 166
167 # Skip blank lines and comments. 167 # Skip blank lines and comments.
168 case $line in 168 case $line in
169 ''|'#'*) 169 ''|'#'*)
170 continue 170 continue
171 ;; 171 ;;
172 esac 172 esac
173 173
174 # Require the first non-blank/comment line to identify 174 # Require the first non-blank/comment line to identify
175 # the config file format. 175 # the config file format.
176 if ! $formatok; then 176 if ! $formatok; then
177 if [ "$line" = "netbsd-certctl 20230816" ]; then 177 if [ "$line" = "netbsd-certctl 20230816" ]; then
178 formatok=true 178 formatok=true
179 continue 179 continue
180 else 180 else
181 error "$vconfig:$lineno: missing format line" 181 error "$vconfig:$lineno: missing format line"
182 status=1 182 status=1
183 break 183 break
184 fi 184 fi
185 fi 185 fi
186 186
187 # Split the line into words and dispatch on the first. 187 # Split the line into words and dispatch on the first.
188 set -- $line 188 set -- $line
189 op=$1 189 op=$1
190 case $op in 190 case $op in
191 manual) 191 manual)
192 config_manual=true 192 config_manual=true
193 ;; 193 ;;
194 path) 194 path)
195 if [ $# -lt 2 ]; then 195 if [ $# -lt 2 ]; then
196 error "$vconfig:$lineno: missing path" 196 error "$vconfig:$lineno: missing path"
197 status=1 197 status=1
198 continue 198 continue
199 fi 199 fi
200 if [ $# -gt 3 ]; then 200 if [ $# -gt 3 ]; then
201 error "$vconfig:$lineno: excess args" 201 error "$vconfig:$lineno: excess args"
202 status=1 202 status=1
203 continue 203 continue
204 fi 204 fi
205 205
206 # Unvis the path. Hack: if the user has had 206 # Unvis the path. Hack: if the user has had
207 # the audacity to choose a path ending in 207 # the audacity to choose a path ending in
208 # newlines, prevent the shell from consuming 208 # newlines, prevent the shell from consuming
209 # them so we don't choke on their subterfuge. 209 # them so we don't choke on their subterfuge.
210 path=$(printf '%s.' "$2" | unvis) 210 path=$(printf '%s.' "$2" | unvis)
211 path=${path%.} 211 path=${path%.}
212 212
213 # Ensure the path is absolute. It is unclear 213 # Ensure the path is absolute. It is unclear
214 # what directory it should be relative to if 214 # what directory it should be relative to if
215 # not. 215 # not.
216 case $path in 216 case $path in
217 /*) 217 /*)
218 ;; 218 ;;
219 *) 219 *)
220 error "$vconfig:$lineno:" \ 220 error "$vconfig:$lineno:" \
221 "relative path forbidden" 221 "relative path forbidden"
222 status=1 222 status=1
223 continue 223 continue
224 ;; 224 ;;
225 esac 225 esac
226 226
227 # Record the vis-encoded path in a 227 # Record the vis-encoded path in a
228 # space-separated list. 228 # space-separated list.
229 vpath=$(printf '%s' "$path" | vis -M) 229 vpath=$(printf '%s' "$path" | vis -M)
230 config_paths="$config_paths $vpath" 230 config_paths="$config_paths $vpath"
231 ;; 231 ;;
232 *) 232 *)
233 vop=$(printf '%s' "$op" | vis -M) 233 vop=$(printf '%s' "$op" | vis -M)
234 error "$vconfig:$lineno: unknown command: $vop" 234 error "$vconfig:$lineno: unknown command: $vop"
235 ;; 235 ;;
236 esac 236 esac
237 done <$config || status=$? 237 done <$config || status=$?
238 238
239 return $status 239 return $status
240} 240}
241 241
242# list_default_trusted 242# list_default_trusted
243# 243#
244# List the vis-encoded certificate paths and their base names, 244# List the vis-encoded certificate paths and their base names,
245# separated by a space, for the certificates that are trusted by 245# separated by a space, for the certificates that are trusted by
246# default according to the configuration. 246# default according to the configuration.
247# 247#
248# No order guaranteed; caller must sort. 248# No order guaranteed; caller must sort.
249# 249#
250list_default_trusted() 250list_default_trusted()
251{ 251{
252 local vpath path cert base vcert vbase 252 local vpath path cert base vcert vbase
253 253
254 for vpath in $config_paths; do 254 for vpath in $config_paths; do
255 path=$(printf '%s.' "$vpath" | unvis) 255 path=$(printf '%s.' "$vpath" | unvis)
256 path=${path%.} 256 path=${path%.}
257 257
258 # Enumerate the .pem, .cer, and .crt files. 258 # Enumerate the .pem, .cer, and .crt files.
259 for cert in "$path"/*.pem "$path"/*.cer "$path"/*.crt; do 259 for cert in "$path"/*.pem "$path"/*.cer "$path"/*.crt; do
260 # vis the certificate path. 260 # vis the certificate path.
261 vcert=$(printf '%s' "$cert" | vis -M) 261 vcert=$(printf '%s' "$cert" | vis -M)
262 262
263 # If the file doesn't exist, then either: 263 # If the file doesn't exist, then either:
264 # 264 #
265 # (a) it's a broken symlink, so fail; 265 # (a) it's a broken symlink, so fail;
266 # or 266 # or
267 # (b) the shell glob failed to match, 267 # (b) the shell glob failed to match,
268 # so ignore it and move on. 268 # so ignore it and move on.
269 if [ ! -e "$cert" ]; then 269 if [ ! -e "$cert" ]; then
270 if [ -h "$cert" ]; then 270 if [ -h "$cert" ]; then
271 error "broken symlink: $vcert" 271 error "broken symlink: $vcert"
272 status=1 272 status=1
273 fi 273 fi
274 continue 274 continue
275 fi 275 fi
276 276
277 # Print the vis-encoded absolute path to the 277 # Print the vis-encoded absolute path to the
278 # certificate and base name on a single line. 278 # certificate and base name on a single line.
279 vbase=${vcert##*/} 279 vbase=${vcert##*/}
280 printf '%s %s\n' "$vcert" "$vbase" 280 printf '%s %s\n' "$vcert" "$vbase"
281 done 281 done
282 done 282 done
283} 283}
284 284
285# list_distrusted 285# list_distrusted
286# 286#
287# List the vis-encoded certificate paths and their base names, 287# List the vis-encoded certificate paths and their base names,
288# separated by a space, for the certificates that have been 288# separated by a space, for the certificates that have been
289# distrusted by the user. 289# distrusted by the user.
290# 290#
291# No order guaranteed; caller must sort. 291# No order guaranteed; caller must sort.
292# 292#
293list_distrusted() 293list_distrusted()
294{ 294{
295 local status link vlink cert vcert 295 local status link vlink cert vcert
296 296
297 status=0 297 status=0
298 298
299 for link in "$distrustdir"/*; do 299 for link in "$distrustdir"/*; do
300 # vis the link for terminal-safe error messages. 300 # vis the link for terminal-safe error messages.
301 vlink=$(printf '%s' "$link" | vis -M) 301 vlink=$(printf '%s' "$link" | vis -M)
302 302
303 # The distrust directory must only have symlinks to 303 # The distrust directory must only have symlinks to
304 # certificates. If we find a non-symlink, print a 304 # certificates. If we find a non-symlink, print a
305 # warning and arrange to fail. 305 # warning and arrange to fail.
306 if [ ! -h "$link" ]; then 306 if [ ! -h "$link" ]; then
307 if [ ! -e "$link" ] && \ 307 if [ ! -e "$link" ] && \
308 [ "$link" = "$distrustdir/*" ]; then 308 [ "$link" = "$distrustdir/*" ]; then
309 # Shell glob matched nothing -- just 309 # Shell glob matched nothing -- just
310 # ignore it. 310 # ignore it.
311 break 311 break
312 fi 312 fi
313 error "distrusted non-symlink: $vlink" 313 error "distrusted non-symlink: $vlink"
314 status=1 314 status=1
315 continue 315 continue
316 fi 316 fi
317 317
318 # Read the target of the symlink, nonrecursively. If 318 # Read the target of the symlink, nonrecursively. If
319 # the user has had the audacity to make a symlink whose 319 # the user has had the audacity to make a symlink whose
320 # target ends in newline, prevent the shell from 320 # target ends in newline, prevent the shell from
321 # consuming them so we don't choke on their subterfuge. 321 # consuming them so we don't choke on their subterfuge.
322 cert=$(readlink -n -- "$link" && printf .) 322 cert=$(readlink -n -- "$link" && printf .)
323 cert=${cert%.} 323 cert=${cert%.}
324 324
325 # Warn if the target is relative. Although it is clear 325 # Warn if the target is relative. Although it is clear
326 # what directory it would be relative to, there might 326 # what directory it would be relative to, there might
327 # be issues with canonicalization. 327 # be issues with canonicalization.
328 case $cert in 328 case $cert in
329 /*) 329 /*)
330 ;; 330 ;;
331 *) 331 *)
332 vlink=$(printf '%s' "$link" | vis -M) 332 vlink=$(printf '%s' "$link" | vis -M)
333 vcert=$(printf '%s' "$cert" | vis -M) 333 vcert=$(printf '%s' "$cert" | vis -M)
334 error "distrusted relative symlink: $vlink -> $vcert" 334 error "distrusted relative symlink: $vlink -> $vcert"
335 ;; 335 ;;
336 esac 336 esac
337 337
338 # Print the vis-encoded absolute path to the 338 # Print the vis-encoded absolute path to the
339 # certificate and base name on a single line. 339 # certificate and base name on a single line.
340 vcert=$(printf '%s' "$cert" | vis -M) 340 vcert=$(printf '%s' "$cert" | vis -M)
341 vbase=${vcert##*/} 341 vbase=${vcert##*/}
342 printf '%s %s\n' "$vcert" "$vbase" 342 printf '%s %s\n' "$vcert" "$vbase"
343 done 343 done
344 344
345 return $status 345 return $status
346} 346}
347 347
348# list_trusted 348# list_trusted
349# 349#
350# List the trusted certificates, excluding the distrusted one, as 350# List the trusted certificates, excluding the distrusted one, as
351# one vis(3) line per certificate. Reject duplicate base names, 351# one vis(3) line per certificate. Reject duplicate base names,
352# since we will be creating symlinks to the same base names in 352# since we will be creating symlinks to the same base names in
353# the certsdir. Sorted lexicographically by vis-encoding. 353# the certsdir. Sorted lexicographically by vis-encoding.
354# 354#
355list_trusted() 355list_trusted()
356{ 356{
357 357
358 # XXX Use dev/ino to match files instead of symlink targets? 358 # XXX Use dev/ino to match files instead of symlink targets?
359 359
360 { 360 {
361 list_default_trusted \ 361 list_default_trusted \
362 | while read -r vcert vbase; do 362 | while read -r vcert vbase; do
363 printf 'trust %s %s\n' "$vcert" "$vbase" 363 printf 'trust %s %s\n' "$vcert" "$vbase"
364 done 364 done
365 365
366 # XXX Find a good way to list the default-untrusted 366 # XXX Find a good way to list the default-untrusted
367 # certificates, so if you have already distrusted one 367 # certificates, so if you have already distrusted one
368 # and it is removed from default-trust on update, 368 # and it is removed from default-trust on update,
369 # nothing warns about this. 369 # nothing warns about this.
370 370
371 # list_default_untrusted \ 371 # list_default_untrusted \
372 # | while read -r vcert vbase; do 372 # | while read -r vcert vbase; do
373 # printf 'distrust %s %s\n' "$vcert" "$vbase" 373 # printf 'distrust %s %s\n' "$vcert" "$vbase"
374 # done 374 # done
375 375
376 list_distrusted \ 376 list_distrusted \
377 | while read -r vcert vbase; do 377 | while read -r vcert vbase; do
378 printf 'distrust %s %s\n' "$vcert" "$vbase" 378 printf 'distrust %s %s\n' "$vcert" "$vbase"
379 done 379 done
380 } | awk -v progname="$progname" ' 380 } | awk -v progname="$progname" '
381 BEGIN { status = 0 } 381 BEGIN { status = 0 }
382 $1 == "trust" && $3 in trust && $2 != trust[$3] { 382 $1 == "trust" && $3 in trust && $2 != trust[$3] {
383 printf "%s: duplicate base name %s\n %s\n %s\n", \ 383 printf "%s: duplicate base name %s\n %s\n %s\n", \
384 progname, $3, trust[$3], $2 >"/dev/stderr" 384 progname, $3, trust[$3], $2 >"/dev/stderr"
385 status = 1 385 status = 1
386 next 386 next
387 } 387 }
388 $1 == "trust" { trust[$3] = $2 } 388 $1 == "trust" { trust[$3] = $2 }
389 $1 == "distrust" && !trust[$3] && !distrust[$3] { 389 $1 == "distrust" && !trust[$3] && !distrust[$3] {
390 printf "%s: distrusted certificate not found: %s\n", \ 390 printf "%s: distrusted certificate not found: %s\n", \
391 progname, $3 >"/dev/stderr" 391 progname, $3 >"/dev/stderr"
392 status = 1 392 status = 1
393 } 393 }
394 $1 == "distrust" && $2 in trust && $2 != trust[$3] { 394 $1 == "distrust" && $2 in trust && $2 != trust[$3] {
395 printf "%s: distrusted certificate %s" \ 395 printf "%s: distrusted certificate %s" \
396 " has multiple paths\n" \ 396 " has multiple paths\n" \
397 " %s\n %s\n", 397 " %s\n %s\n",
398 progname, $3, trust[$3], $2 >"/dev/stderr" 398 progname, $3, trust[$3], $2 >"/dev/stderr"
399 status = 1 399 status = 1
400 } 400 }
401 $1 == "distrust" { distrust[$3] = 1 } 401 $1 == "distrust" { distrust[$3] = 1 }
402 END { 402 END {
403 for (vbase in trust) { 403 for (vbase in trust) {
404 if (!distrust[vbase]) 404 if (!distrust[vbase])
405 print trust[vbase] 405 print trust[vbase]
406 } 406 }
407 exit status 407 exit status
408 } 408 }
409 ' | sort -u 409 ' | sort -u
410} 410}
411 411
412# rehash 412# rehash
413# 413#
414# Delete and rebuild certsdir. 414# Delete and rebuild certsdir.
415# 415#
416rehash() 416rehash()
417{ 417{
418 local vcert cert certbase hash counter bundle vbundle 418 local vcert cert certbase hash counter bundle vbundle
419 419
420 # If manual operation is enabled, refuse to rehash the 420 # If manual operation is enabled, refuse to rehash the
421 # certsdir, but succeed anyway so this can safely be used in 421 # certsdir, but succeed anyway so this can safely be used in
422 # automated scripts. 422 # automated scripts.
423 if $config_manual; then 423 if $config_manual; then
424 error "manual certificates enabled, not rehashing" 424 error "manual certificates enabled, not rehashing"
425 return 425 return
426 fi 426 fi
427 427
428 # Delete the active certificates symlink cache, if either it is 428 # Delete the active certificates symlink cache, if either it is
429 # empty or nonexistent, or it is tagged for use by certctl. 429 # empty or nonexistent, or it is tagged for use by certctl.
430 if [ -f "$certsdir/.certctl" ]; then 430 if [ -f "$certsdir/.certctl" ]; then
431 # Directory exists and is managed by certctl(8). 431 # Directory exists and is managed by certctl(8).
432 # Safe to delete it and everything in it. 432 # Safe to delete it and everything in it.
433 run rm -rf -- "$certsdir" 433 run rm -rf -- "$certsdir"
434 elif [ -h "$certsdir" ]; then 434 elif [ -h "$certsdir" ]; then
435 # Paranoia: refuse to chase a symlink. (Caveat: this 435 # Paranoia: refuse to chase a symlink. (Caveat: this
436 # is not secure against an adversary who can recreate 436 # is not secure against an adversary who can recreate
437 # the symlink at any time. Just a helpful check for 437 # the symlink at any time. Just a helpful check for
438 # mistakes.) 438 # mistakes.)
439 error "certificates directory is a symlink" 439 error "certificates directory is a symlink"
440 return 1 440 return 1
441 elif [ ! -e "$certsdir" ]; then 441 elif [ ! -e "$certsdir" ]; then
442 # Directory doesn't exist at all. Nothing to do! 442 # Directory doesn't exist at all. Nothing to do!
 443 :
443 elif [ ! -d "$certsdir" ]; then 444 elif [ ! -d "$certsdir" ]; then
444 error "certificates directory is not a directory" 445 error "certificates directory is not a directory"
445 return 1 446 return 1
446 elif ! find -f "$certsdir" -- -maxdepth 0 -type d -empty -exit 1; then 447 elif ! find -f "$certsdir" -- -maxdepth 0 -type d -empty -exit 1; then
447 # certsdir exists, is a directory, and is empty. Safe 448 # certsdir exists, is a directory, and is empty. Safe
448 # to delete it with rmdir and take it over. 449 # to delete it with rmdir and take it over.
449 run rmdir -- "$certsdir" 450 run rmdir -- "$certsdir"
450 else 451 else
451 error "existing certificates; set manual or move them" 452 error "existing certificates; set manual or move them"
452 return 1 453 return 1
453 fi 454 fi
454 run mkdir -- "$certsdir" 455 run mkdir -- "$certsdir"
455 if $vflag; then 456 if $vflag; then
456 printf '# initialize %s\n' "$certsdir" 457 printf '# initialize %s\n' "$certsdir"
457 fi 458 fi
458 if ! $nflag; then 459 if ! $nflag; then
459 printf 'This directory is managed by certctl(8).\n' \ 460 printf 'This directory is managed by certctl(8).\n' \
460 >$certsdir/.certctl 461 >$certsdir/.certctl
461 fi 462 fi
462 463
463 # Create a temporary file for the single-file bundle. This 464 # Create a temporary file for the single-file bundle. This
464 # will be automatically deleted on normal exit or 465 # will be automatically deleted on normal exit or
465 # SIGHUP/SIGINT/SIGTERM. 466 # SIGHUP/SIGINT/SIGTERM.
466 if ! $nflag; then 467 if ! $nflag; then
467 tmpfile=$(mktemp -t "$progname.XXXXXX") 468 tmpfile=$(mktemp -t "$progname.XXXXXX")
468 fi 469 fi
469 470
470 # Recreate symlinks for all of the trusted certificates. 471 # Recreate symlinks for all of the trusted certificates.
471 list_trusted \ 472 list_trusted \
472 | while read -r vcert; do 473 | while read -r vcert; do
473 cert=$(printf '%s.' "$vcert" | unvis) 474 cert=$(printf '%s.' "$vcert" | unvis)
474 cert=${cert%.} 475 cert=${cert%.}
475 run ln -s -- "$cert" "$certsdir" 476 run ln -s -- "$cert" "$certsdir"
476 477
477 # Add the certificate to the single-file bundle. 478 # Add the certificate to the single-file bundle.
478 if ! $nflag; then 479 if ! $nflag; then
479 cat -- "$cert" >>$tmpfile 480 cat -- "$cert" >>$tmpfile
480 fi 481 fi
481 done 482 done
482 483
483 # Hash the directory with openssl. 484 # Hash the directory with openssl.
484 # 485 #
485 # XXX Pass `-v' to openssl in a way that doesn't mix with our 486 # XXX Pass `-v' to openssl in a way that doesn't mix with our
486 # shell-safe verbose commands? (Need to handle `-n' too.) 487 # shell-safe verbose commands? (Need to handle `-n' too.)
487 run openssl rehash -- "$certsdir" 488 run openssl rehash -- "$certsdir"
488 489
489 # Install the single-file bundle. 490 # Install the single-file bundle.
490 bundle=$certsdir/ca-certificates.crt 491 bundle=$certsdir/ca-certificates.crt
491 vbundle=$(printf '%s' "$bundle" | vis -M) 492 vbundle=$(printf '%s' "$bundle" | vis -M)
492 $vflag && printf '# create %s\n' "$vbundle" 493 $vflag && printf '# create %s\n' "$vbundle"
493 if ! $nflag; then 494 if ! $nflag; then
494 (umask 0022; cat <$tmpfile >${bundle}.tmp) 495 (umask 0022; cat <$tmpfile >${bundle}.tmp)
495 mv -f -- "${bundle}.tmp" "$bundle" 496 mv -f -- "${bundle}.tmp" "$bundle"
496 rm -f -- "$tmpfile" 497 rm -f -- "$tmpfile"
497 tmpfile= 498 tmpfile=
498 fi 499 fi
499} 500}
500 501
501### Commands 502### Commands
502 503
503usage_list() 504usage_list()
504{ 505{
505 exec >&2 506 exec >&2
506 printf 'Usage: %s list\n' "$progname" 507 printf 'Usage: %s list\n' "$progname"
507 exit 1 508 exit 1
508} 509}
509cmd_list() 510cmd_list()
510{ 511{
511 test $# -eq 1 || usage_list 512 test $# -eq 1 || usage_list
512 513
513 configure 514 configure
514 515
515 list_trusted \ 516 list_trusted \
516 | while read -r vcert vbase; do 517 | while read -r vcert vbase; do
517 printf '%s\n' "$vcert" 518 printf '%s\n' "$vcert"
518 done 519 done
519} 520}
520 521
521usage_rehash() 522usage_rehash()
522{ 523{
523 exec >&2 524 exec >&2
524 printf 'Usage: %s rehash\n' "$progname" 525 printf 'Usage: %s rehash\n' "$progname"
525 exit 1 526 exit 1
526} 527}
527cmd_rehash() 528cmd_rehash()
528{ 529{
529 test $# -eq 1 || usage_rehash 530 test $# -eq 1 || usage_rehash
530 531
531 configure 532 configure
532 533
533 rehash 534 rehash
534} 535}
535 536
536usage_trust() 537usage_trust()
537{ 538{
538 exec >&2 539 exec >&2
539 printf 'Usage: %s trust <cert>\n' "$progname" 540 printf 'Usage: %s trust <cert>\n' "$progname"
540 exit 1 541 exit 1
541} 542}
542cmd_trust() 543cmd_trust()
543{ 544{
544 local cert vcert certbase vcertbase 545 local cert vcert certbase vcertbase
545 546
546 test $# -eq 2 || usage_trust 547 test $# -eq 2 || usage_trust
547 cert=$2 548 cert=$2
548 549
549 configure 550 configure
550 551
551 # XXX Accept base name. 552 # XXX Accept base name.
552 553
553 # vis the certificate path for terminal-safe error messages. 554 # vis the certificate path for terminal-safe error messages.
554 vcert=$(printf '%s' "$cert" | vis -M) 555 vcert=$(printf '%s' "$cert" | vis -M)
555 556
556 # Verify the certificate actually exists. 557 # Verify the certificate actually exists.
557 if [ ! -f "$cert" ]; then 558 if [ ! -f "$cert" ]; then
558 error "no such certificate: $vcert" 559 error "no such certificate: $vcert"
559 return 1 560 return 1
560 fi 561 fi
561 562
562 # Verify we currently distrust a certificate by this base name. 563 # Verify we currently distrust a certificate by this base name.
563 certbase=${cert##*/} 564 certbase=${cert##*/}
564 if [ ! -h "$distrustdir/$certbase" ]; then 565 if [ ! -h "$distrustdir/$certbase" ]; then
565 error "not currently distrusted: $vcert" 566 error "not currently distrusted: $vcert"
566 return 1 567 return 1
567 fi 568 fi
568 569
569 # Verify the certificate we distrust by this base name is the 570 # Verify the certificate we distrust by this base name is the
570 # same one. 571 # same one.
571 target=$(readlink -n -- "$distrustdir/$certbase" && printf .) 572 target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
572 target=${target%.} 573 target=${target%.}
573 if [ "$cert" != "$target" ]; then 574 if [ "$cert" != "$target" ]; then
574 vcertbase=${vcert##*/} 575 vcertbase=${vcert##*/}
575 error "distrusted $vcertbase does not point to $vcert" 576 error "distrusted $vcertbase does not point to $vcert"
576 return 1 577 return 1
577 fi 578 fi
578 579
579 # Remove the link from the distrusted directory, and rehash -- 580 # Remove the link from the distrusted directory, and rehash --
580 # quietly, so verbose output emphasizes the distrust part and 581 # quietly, so verbose output emphasizes the distrust part and
581 # not the whole certificate set. 582 # not the whole certificate set.
582 run rm -- "$distrustdir/$certbase" 583 run rm -- "$distrustdir/$certbase"
583 $vflag && echo '# rehash' 584 $vflag && echo '# rehash'
584 vflag=false 585 vflag=false
585 rehash 586 rehash
586} 587}
587 588
588usage_untrust() 589usage_untrust()
589{ 590{
590 exec >&2 591 exec >&2
591 printf 'Usage: %s untrust <cert>\n' "$progname" 592 printf 'Usage: %s untrust <cert>\n' "$progname"
592 exit 1 593 exit 1
593} 594}
594cmd_untrust() 595cmd_untrust()
595{ 596{
596 local cert vcert certbase vcertbase target vtarget 597 local cert vcert certbase vcertbase target vtarget
597 598
598 test $# -eq 2 || usage_untrust 599 test $# -eq 2 || usage_untrust
599 cert=$2 600 cert=$2
600 601
601 configure 602 configure
602 603
603 # vis the certificate path for terminal-safe error messages. 604 # vis the certificate path for terminal-safe error messages.
604 vcert=$(printf '%s' "$cert" | vis -M) 605 vcert=$(printf '%s' "$cert" | vis -M)
605 606
606 # Verify the certificate actually exists. Otherwise, you might 607 # Verify the certificate actually exists. Otherwise, you might
607 # fail to distrust a certificate you intended to distrust, 608 # fail to distrust a certificate you intended to distrust,
608 # e.g. if you made a typo in its path. 609 # e.g. if you made a typo in its path.
609 if [ ! -f "$cert" ]; then 610 if [ ! -f "$cert" ]; then
610 error "no such certificate: $vcert" 611 error "no such certificate: $vcert"
611 return 1 612 return 1
612 fi 613 fi
613 614
614 # Check whether this certificate is already distrusted. 615 # Check whether this certificate is already distrusted.
615 # - If the same base name points to the same path, stop here. 616 # - If the same base name points to the same path, stop here.
616 # - Otherwise, fail noisily. 617 # - Otherwise, fail noisily.
617 certbase=${cert##*/} 618 certbase=${cert##*/}
618 if [ -h "$distrustdir/$certbase" ]; then 619 if [ -h "$distrustdir/$certbase" ]; then
619 target=$(readlink -n -- "$distrustdir/$certbase" && printf .) 620 target=$(readlink -n -- "$distrustdir/$certbase" && printf .)
620 target=${target%.} 621 target=${target%.}
621 if [ "$target" = "$cert" ]; then 622 if [ "$target" = "$cert" ]; then
622 $vflag && echo '# already distrusted' 623 $vflag && echo '# already distrusted'
623 return 624 return
624 fi 625 fi
625 vcertbase=$(printf '%s' "$certbase" | vis -M) 626 vcertbase=$(printf '%s' "$certbase" | vis -M)
626 vtarget=$(printf '%s' "$target" | vis -M) 627 vtarget=$(printf '%s' "$target" | vis -M)
627 error "distrusted $vcertbase at different path $vtarget" 628 error "distrusted $vcertbase at different path $vtarget"
628 return 1 629 return 1
629 fi 630 fi
630 631
631 # Create the distrustdir if needed, create a symlink in it, and 632 # Create the distrustdir if needed, create a symlink in it, and
632 # rehash -- quietly, so verbose output emphasizes the distrust 633 # rehash -- quietly, so verbose output emphasizes the distrust
633 # part and not the whole certificate set. 634 # part and not the whole certificate set.
634 test -d "$distrustdir" || run mkdir -- "$distrustdir" 635 test -d "$distrustdir" || run mkdir -- "$distrustdir"
635 run ln -s -- "$cert" "$distrustdir" 636 run ln -s -- "$cert" "$distrustdir"
636 $vflag && echo '# rehash' 637 $vflag && echo '# rehash'
637 vflag=false 638 vflag=false
638 rehash 639 rehash
639} 640}
640 641
641usage_untrusted() 642usage_untrusted()
642{ 643{
643 exec >&2 644 exec >&2
644 printf 'Usage: %s untrusted\n' "$progname" 645 printf 'Usage: %s untrusted\n' "$progname"
645 exit 1 646 exit 1
646} 647}
647cmd_untrusted() 648cmd_untrusted()
648{ 649{
649 test $# -eq 1 || usage_untrusted 650 test $# -eq 1 || usage_untrusted
650 651
651 configure 652 configure
652 653
653 list_distrusted \ 654 list_distrusted \
654 | while read -r vcert vbase; do 655 | while read -r vcert vbase; do
655 printf '%s\n' "$vcert" 656 printf '%s\n' "$vcert"
656 done 657 done
657} 658}
658 659
659### Main 660### Main
660 661
661# We accept the following aliases for user interface compatibility with 662# We accept the following aliases for user interface compatibility with
662# FreeBSD: 663# FreeBSD:
663# 664#
664# blacklist = untrust 665# blacklist = untrust
665# blacklisted = untrusted 666# blacklisted = untrusted
666# unblacklist = trust 667# unblacklist = trust
667 668
668case $cmd in 669case $cmd in
669list) cmd_list "$@" 670list) cmd_list "$@"
670 ;; 671 ;;
671rehash) cmd_rehash "$@" 672rehash) cmd_rehash "$@"
672 ;; 673 ;;
673trust|unblacklist) 674trust|unblacklist)
674 cmd_trust "$@" 675 cmd_trust "$@"
675 ;; 676 ;;
676untrust|blacklist) 677untrust|blacklist)
677 cmd_untrust "$@" 678 cmd_untrust "$@"
678 ;; 679 ;;
679untrusted|blacklisted) 680untrusted|blacklisted)
680 cmd_untrusted "$@" 681 cmd_untrusted "$@"
681 ;; 682 ;;
682*) vcmd=$(printf '%s' "$cmd" | vis -M) 683*) vcmd=$(printf '%s' "$cmd" | vis -M)
683 printf '%s: unknown command: %s\n' "$progname" "$vcmd" >&2 684 printf '%s: unknown command: %s\n' "$progname" "$vcmd" >&2
684 usage 685 usage
685 ;; 686 ;;
686esac 687esac