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