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