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