/src/sudo/lib/util/fatal.c
Line | Count | Source |
1 | | /* |
2 | | * SPDX-License-Identifier: ISC |
3 | | * |
4 | | * Copyright (c) 2004-2005, 2010-2015, 2017-2018 |
5 | | * Todd C. Miller <Todd.Miller@sudo.ws> |
6 | | * |
7 | | * Permission to use, copy, modify, and distribute this software for any |
8 | | * purpose with or without fee is hereby granted, provided that the above |
9 | | * copyright notice and this permission notice appear in all copies. |
10 | | * |
11 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
12 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
14 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
16 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
17 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 | | */ |
19 | | |
20 | | #include <config.h> |
21 | | |
22 | | #include <errno.h> |
23 | | #include <netdb.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | #ifdef HAVE_STDBOOL_H |
28 | | # include <stdbool.h> |
29 | | #else |
30 | | # include <compat/stdbool.h> |
31 | | #endif /* HAVE_STDBOOL_H */ |
32 | | #include <unistd.h> |
33 | | #ifndef HAVE_GETADDRINFO |
34 | | # include <compat/getaddrinfo.h> |
35 | | #endif |
36 | | |
37 | | #include <sudo_compat.h> |
38 | | #include <sudo_fatal.h> |
39 | | #include <sudo_gettext.h> |
40 | | #include <sudo_queue.h> |
41 | | #include <sudo_util.h> |
42 | | #include <sudo_plugin.h> |
43 | | |
44 | | struct sudo_fatal_callback { |
45 | | SLIST_ENTRY(sudo_fatal_callback) entries; |
46 | | void (*func)(void); |
47 | | }; |
48 | | SLIST_HEAD(sudo_fatal_callback_list, sudo_fatal_callback); |
49 | | |
50 | | static struct sudo_fatal_callback_list callbacks = SLIST_HEAD_INITIALIZER(&callbacks); |
51 | | static sudo_conv_t sudo_warn_conversation; |
52 | | static sudo_warn_setlocale_t sudo_warn_setlocale; |
53 | | static sudo_warn_setlocale_t sudo_warn_setlocale_prev; |
54 | | |
55 | | static void warning(const char * restrict errstr, const char * restrict fmt, va_list ap); |
56 | | |
57 | | static void |
58 | | do_cleanup(void) |
59 | 0 | { |
60 | 0 | struct sudo_fatal_callback *cb; |
61 | | |
62 | | /* Run callbacks, removing them from the list as we go. */ |
63 | 0 | while ((cb = SLIST_FIRST(&callbacks)) != NULL) { |
64 | 0 | SLIST_REMOVE_HEAD(&callbacks, entries); |
65 | 0 | cb->func(); |
66 | 0 | free(cb); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | sudo_noreturn void |
71 | | sudo_fatal_nodebug_v1(const char * restrict fmt, ...) |
72 | 0 | { |
73 | 0 | va_list ap; |
74 | |
|
75 | 0 | va_start(ap, fmt); |
76 | 0 | warning(strerror(errno), fmt, ap); |
77 | 0 | va_end(ap); |
78 | 0 | do_cleanup(); |
79 | 0 | exit(EXIT_FAILURE); |
80 | 0 | } |
81 | | |
82 | | sudo_noreturn void |
83 | | sudo_fatalx_nodebug_v1(const char * restrict fmt, ...) |
84 | 0 | { |
85 | 0 | va_list ap; |
86 | |
|
87 | 0 | va_start(ap, fmt); |
88 | 0 | warning(NULL, fmt, ap); |
89 | 0 | va_end(ap); |
90 | 0 | do_cleanup(); |
91 | 0 | exit(EXIT_FAILURE); |
92 | 0 | } |
93 | | |
94 | | sudo_noreturn void |
95 | | sudo_vfatal_nodebug_v1(const char * restrict fmt, va_list ap) |
96 | 0 | { |
97 | 0 | warning(strerror(errno), fmt, ap); |
98 | 0 | do_cleanup(); |
99 | 0 | exit(EXIT_FAILURE); |
100 | 0 | } |
101 | | |
102 | | sudo_noreturn void |
103 | | sudo_vfatalx_nodebug_v1(const char * restrict fmt, va_list ap) |
104 | 0 | { |
105 | 0 | warning(NULL, fmt, ap); |
106 | 0 | do_cleanup(); |
107 | 0 | exit(EXIT_FAILURE); |
108 | 0 | } |
109 | | |
110 | | void |
111 | | sudo_warn_nodebug_v1(const char * restrict fmt, ...) |
112 | 309 | { |
113 | 309 | va_list ap; |
114 | | |
115 | 309 | va_start(ap, fmt); |
116 | 309 | warning(strerror(errno), fmt, ap); |
117 | 309 | va_end(ap); |
118 | 309 | } |
119 | | |
120 | | void |
121 | | sudo_warnx_nodebug_v1(const char * restrict fmt, ...) |
122 | 0 | { |
123 | 0 | va_list ap; |
124 | 0 | va_start(ap, fmt); |
125 | 0 | warning(NULL, fmt, ap); |
126 | 0 | va_end(ap); |
127 | 0 | } |
128 | | |
129 | | void |
130 | | sudo_vwarn_nodebug_v1(const char * restrict fmt, va_list ap) |
131 | 0 | { |
132 | 0 | warning(strerror(errno), fmt, ap); |
133 | 0 | } |
134 | | |
135 | | void |
136 | | sudo_vwarnx_nodebug_v1(const char * restrict fmt, va_list ap) |
137 | 0 | { |
138 | 0 | warning(NULL, fmt, ap); |
139 | 0 | } |
140 | | |
141 | | sudo_noreturn void |
142 | | sudo_gai_fatal_nodebug_v1(int errnum, const char * restrict fmt, ...) |
143 | 0 | { |
144 | 0 | va_list ap; |
145 | |
|
146 | 0 | va_start(ap, fmt); |
147 | 0 | warning(gai_strerror(errnum), fmt, ap); |
148 | 0 | va_end(ap); |
149 | 0 | do_cleanup(); |
150 | 0 | exit(EXIT_FAILURE); |
151 | 0 | } |
152 | | |
153 | | sudo_noreturn void |
154 | | sudo_gai_vfatal_nodebug_v1(int errnum, const char * restrict fmt, va_list ap) |
155 | 0 | { |
156 | 0 | warning(gai_strerror(errnum), fmt, ap); |
157 | 0 | do_cleanup(); |
158 | 0 | exit(EXIT_FAILURE); |
159 | 0 | } |
160 | | |
161 | | void |
162 | | sudo_gai_warn_nodebug_v1(int errnum, const char * restrict fmt, ...) |
163 | 0 | { |
164 | 0 | va_list ap; |
165 | |
|
166 | 0 | va_start(ap, fmt); |
167 | 0 | warning(gai_strerror(errnum), fmt, ap); |
168 | 0 | va_end(ap); |
169 | 0 | } |
170 | | |
171 | | void |
172 | | sudo_gai_vwarn_nodebug_v1(int errnum, const char * restrict fmt, va_list ap) |
173 | 0 | { |
174 | 0 | warning(gai_strerror(errnum), fmt, ap); |
175 | 0 | } |
176 | | |
177 | | static void |
178 | | warning(const char * restrict errstr, const char * restrict fmt, va_list ap) |
179 | 309 | { |
180 | 309 | int cookie; |
181 | 309 | const int saved_errno = errno; |
182 | | |
183 | | /* Set user locale if setter was specified. */ |
184 | 309 | if (sudo_warn_setlocale != NULL) |
185 | 0 | sudo_warn_setlocale(false, &cookie); |
186 | | |
187 | 309 | if (sudo_warn_conversation != NULL) { |
188 | 309 | struct sudo_conv_message msgs[6]; |
189 | 309 | char static_buf[1024], *buf = static_buf; |
190 | 309 | int nmsgs = 0; |
191 | | |
192 | | /* Use conversation function. */ |
193 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
194 | 309 | msgs[nmsgs++].msg = getprogname(); |
195 | 309 | if (fmt != NULL) { |
196 | 309 | va_list ap2; |
197 | 309 | int buflen; |
198 | | |
199 | | /* Use static buffer if possible, else dynamic. */ |
200 | 309 | va_copy(ap2, ap); |
201 | 309 | buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2); |
202 | 309 | va_end(ap2); |
203 | 309 | if (buflen >= ssizeof(static_buf)) { |
204 | | /* Not enough room in static buf, allocate dynamically. */ |
205 | 29 | if (vasprintf(&buf, fmt, ap) == -1) |
206 | 0 | buf = static_buf; |
207 | 29 | } |
208 | 309 | if (buflen > 0) { |
209 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
210 | 309 | msgs[nmsgs++].msg = ": "; |
211 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
212 | 309 | msgs[nmsgs++].msg = buf; |
213 | 309 | } |
214 | 309 | } |
215 | 309 | if (errstr != NULL) { |
216 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
217 | 309 | msgs[nmsgs++].msg = ": "; |
218 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
219 | 309 | msgs[nmsgs++].msg = errstr; |
220 | 309 | } |
221 | 309 | msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; |
222 | 309 | msgs[nmsgs++].msg = "\n"; |
223 | 309 | sudo_warn_conversation(nmsgs, msgs, NULL, NULL); |
224 | 309 | if (buf != static_buf) |
225 | 29 | free(buf); |
226 | 309 | } else { |
227 | | /* Write to the standard error. */ |
228 | 0 | fputs(getprogname(), stderr); |
229 | 0 | if (fmt != NULL) { |
230 | 0 | fputs(": ", stderr); |
231 | 0 | vfprintf(stderr, fmt, ap); |
232 | 0 | } |
233 | 0 | if (errstr != NULL) { |
234 | 0 | fputs(": ", stderr); |
235 | 0 | fputs(errstr, stderr); |
236 | 0 | } |
237 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
238 | | if (sudo_term_is_raw(fileno(stderr))) |
239 | | putc('\r', stderr); |
240 | | #endif |
241 | 0 | putc('\n', stderr); |
242 | 0 | } |
243 | | |
244 | | /* Restore old locale as needed. */ |
245 | 309 | if (sudo_warn_setlocale != NULL) |
246 | 0 | sudo_warn_setlocale(true, &cookie); |
247 | | |
248 | | /* Do not clobber errno. */ |
249 | 309 | errno = saved_errno; |
250 | 309 | } |
251 | | |
252 | | /* |
253 | | * Register a callback to be run when sudo_fatal()/sudo_fatalx() is called. |
254 | | */ |
255 | | int |
256 | | sudo_fatal_callback_register_v1(sudo_fatal_callback_t func) |
257 | 0 | { |
258 | 0 | struct sudo_fatal_callback *cb; |
259 | | |
260 | | /* Do not register the same callback twice. */ |
261 | 0 | SLIST_FOREACH(cb, &callbacks, entries) { |
262 | 0 | if (func == cb->func) |
263 | 0 | return -1; /* dupe! */ |
264 | 0 | } |
265 | | |
266 | | /* Allocate and insert new callback. */ |
267 | 0 | cb = malloc(sizeof(*cb)); |
268 | 0 | if (cb == NULL) |
269 | 0 | return -1; |
270 | 0 | cb->func = func; |
271 | 0 | SLIST_INSERT_HEAD(&callbacks, cb, entries); |
272 | |
|
273 | 0 | return 0; |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * Deregister a sudo_fatal()/sudo_fatalx() callback. |
278 | | */ |
279 | | int |
280 | | sudo_fatal_callback_deregister_v1(sudo_fatal_callback_t func) |
281 | 0 | { |
282 | 0 | struct sudo_fatal_callback *cb, *prev = NULL; |
283 | | |
284 | | /* Search for callback and remove if found, dupes are not allowed. */ |
285 | 0 | SLIST_FOREACH(cb, &callbacks, entries) { |
286 | 0 | if (cb->func == func) { |
287 | 0 | if (prev == NULL) |
288 | 0 | SLIST_REMOVE_HEAD(&callbacks, entries); |
289 | 0 | else |
290 | 0 | SLIST_REMOVE_AFTER(prev, entries); |
291 | 0 | free(cb); |
292 | 0 | return 0; |
293 | 0 | } |
294 | 0 | prev = cb; |
295 | 0 | } |
296 | | |
297 | 0 | return -1; |
298 | 0 | } |
299 | | |
300 | | /* |
301 | | * Set the conversation function to use for output instead of the |
302 | | * standard error. If conv is NULL, switch back to standard error. |
303 | | */ |
304 | | void |
305 | | sudo_warn_set_conversation_v1(sudo_conv_t conv) |
306 | 392 | { |
307 | 392 | sudo_warn_conversation = conv; |
308 | 392 | } |
309 | | |
310 | | /* |
311 | | * Set the locale function so the plugin can use a non-default |
312 | | * locale for user warnings. |
313 | | */ |
314 | | void |
315 | | sudo_warn_set_locale_func_v1(sudo_warn_setlocale_t func) |
316 | 0 | { |
317 | 0 | sudo_warn_setlocale_prev = sudo_warn_setlocale; |
318 | 0 | sudo_warn_setlocale = func; |
319 | 0 | } |
320 | | |
321 | | #ifdef HAVE_LIBINTL_H |
322 | | char * |
323 | | sudo_warn_gettext_v1(const char *domainname, const char *msgid) |
324 | 309 | { |
325 | 309 | int cookie; |
326 | 309 | char *msg; |
327 | | |
328 | | /* Set user locale if setter was specified. */ |
329 | 309 | if (sudo_warn_setlocale != NULL) |
330 | 0 | sudo_warn_setlocale(false, &cookie); |
331 | | |
332 | 309 | msg = dgettext(domainname, msgid); |
333 | | |
334 | | /* Restore old locale as needed. */ |
335 | 309 | if (sudo_warn_setlocale != NULL) |
336 | 0 | sudo_warn_setlocale(true, &cookie); |
337 | | |
338 | 309 | return msg; |
339 | 309 | } |
340 | | #endif /* HAVE_LIBINTL_H */ |