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