blob: a3bdb2d80cfc8d58d8e7c819614b3489a899edb8 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
#!/bin/sh -e
help() {
cat - >&2 <<EOF
doasedit - edit non-user-editable files with an unprivileged editor
usage: doasedit -h | -V
usage: doasedit file ...
Options:
-h, --help display help message and exit
-V, --version display version information and exit
-- stop processing command line arguments
Environment Variables:
DOAS_EDITOR program used to edit files
EDITOR program used to edit files if DOAS_EDITOR is unset
To work properly doasedit needs to always start a new editor instance. Some
editors, graphical ones in particular, open files in previously running
instances. If so, append a command line argument to your (DOAS_)EDITOR variable
such that the editor will always start a new instance (e. g.: 'kate -n').
How it works:
Every File to be edited is duplicated to a user owned file in /tmp. The editor
is then run in user context. After closing the editor the user file replaces
the original file while preserving file attributes. All this is done using doas
as little as possible. Files are edited one after another, not all at once.
EOF
}
# Checks for syntax errors in doas' config
#
# check_doas_conf <target> <tmp_target>
#
check_doas_conf() {
if printf '%s' "${1}" | grep -q '^/etc/doas\(\.d/.*\)\?\.conf$'; then
while ! doas -C "${2}"; do
printf "doasedit: Replacing '%s' would " "$file"
printf 'introduce the above error and break doas.\n'
printf '(E)dit again, (O)verwrite anyway, (A)bort: [E/o/a]? '
read -r choice
case "$choice" in
o | O)
return 0
;;
a | A)
return 1
;;
e | E | *)
"$editor_cmd" "$tmpfile"
;;
esac
done
fi
return 0
}
error() {
printf 'doasedit: %s\n' "${@}" 1>&2
}
_exit() {
rm -rf "$tmpdir"
trap - EXIT HUP QUIT TERM INT ABRT
exit "${1:-0}"
}
# no argument passed
[ "${#}" -eq 0 ] && help && exit 1
while [ "${#}" -ne 0 ]; do
case "${1}" in
--)
shift
break
;;
--help | -h)
help
exit 0
;;
--version | -V)
printf 'doasedit version 1.0.7\n'
exit 0
;;
-*)
printf "doasedit: invalid option: '%s'\n" "${1}"
help
exit 1
;;
*)
break
;;
esac
done
[ "$DOAS_EDITOR" != "" ] && editor_cmd="$DOAS_EDITOR" || editor_cmd="$EDITOR"
# shellcheck disable=SC2086
if [ "$editor_cmd" = "" ]; then
if command -v vi >/dev/null 2>&1; then
editor_cmd='vi'
else
error 'no editor specified'
exit 1
fi
elif ! command -v "$editor_cmd" >/dev/null 2>&1; then
error "invalid editor command: '${editor_cmd}'"
exit 1
fi
exit_code=1
trap '_exit "${exit_code}"' EXIT
trap '_exit 130' HUP QUIT TERM INT ABRT
tmpdir="$(mktemp -dt 'doasedit-XXXXXX')"
for file; do
unset exists readable writable
dir="$(dirname -- "$file")"
tmpfile="${tmpdir}/${file##*/}"
tmpfile_copy="${tmpdir}/copy-of-${file##*/}"
printf '' | tee "$tmpfile" >"$tmpfile_copy"
chmod 0600 "$tmpfile" "$tmpfile_copy"
if [ -e "$file" ]; then
if ! [ -f "$file" ]; then
error "${file}: not a regular file"
continue
fi
# -O is not POSIX, but implemented at least in GNU, *BSD and macOS test
if [ -O "$file" ]; then
error "${file}: editing your own files is not permitted"
continue
fi
exists=1
elif doas [ -e "$file" ]; then
if ! doas [ -f "$file" ]; then
error "${file}: not a regular file"
continue
fi
exists=0
else
# New file?
if [ -O "$dir" ]; then
error "${file}: creating files in your own directory is not permitted"
continue
elif [ -x "$dir" ] && [ -w "$dir" ]; then
error "${file}: creating files in a user-writable directory is not permitted"
continue
elif ! doas [ -e "$dir" ]; then
error "${file}: no such directory"
continue
# else: root-writable directory
fi
fi
# If this test is true, it's an existent regular file
if [ "$exists" != "" ]; then
if [ -w "$file" ]; then
writable=1
# Check in advance to make sure that it won't fail after editing.
elif ! doas dd status=none count=0 of=/dev/null; then
error "unable to run 'doas dd'"
continue
fi
if [ -r "$file" ]; then
if [ "$writable" != "" ]; then
error "${file}: editing user-readable and -writable files is not permitted"
continue
fi
# Read file
cat -- "$file" >"$tmpfile"
# Better not suppress stderr here as there might be something of importance.
elif ! doas cat -- "$file" >"$tmpfile"; then
error "you are not permitted to call 'doas cat'"
continue
fi
cat "$tmpfile" >"$tmpfile_copy"
fi
"$editor_cmd" "$tmpfile"
check_doas_conf "$file" "$tmpfile" || continue
if cmp -s "$tmpfile" "$tmpfile_copy"; then
printf 'doasedit: %s: unchanged\n' "$file"
else
if [ "$writable" != "" ]; then
dd status=none if="$tmpfile" of="$file"
else
for de_tries in 2 1 0; do
if doas dd status=none if="$tmpfile" of="$file"; then
break
elif [ "$de_tries" -eq 0 ]; then
error '3 incorrect password attempts'
exit 1
fi
done
fi
fi
exit_code=0
done
# vim: shiftwidth=2 tabstop=2 noexpandtab
|