#include <config.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include <compat/stdbool.h>
#endif
#include <unistd.h>
#ifndef HAVE_GETADDRINFO
# include <compat/getaddrinfo.h>
#endif
#include <sudo_compat.h>
#include <sudo_fatal.h>
#include <sudo_gettext.h>
#include <sudo_queue.h>
#include <sudo_util.h>
#include <sudo_plugin.h>
struct sudo_fatal_callback {
SLIST_ENTRY(sudo_fatal_callback) entries;
void (*func)(void);
};
SLIST_HEAD(sudo_fatal_callback_list, sudo_fatal_callback);
static struct sudo_fatal_callback_list callbacks = SLIST_HEAD_INITIALIZER(&callbacks);
static sudo_conv_t sudo_warn_conversation;
static sudo_warn_setlocale_t sudo_warn_setlocale;
static sudo_warn_setlocale_t sudo_warn_setlocale_prev;
static void warning(const char * restrict errstr, const char * restrict fmt, va_list ap);
static void
do_cleanup(void)
{
struct sudo_fatal_callback *cb;
while ((cb = SLIST_FIRST(&callbacks)) != NULL) {
SLIST_REMOVE_HEAD(&callbacks, entries);
cb->func();
free(cb);
}
}
sudo_noreturn void
sudo_fatal_nodebug_v1(const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(strerror(errno), fmt, ap);
va_end(ap);
do_cleanup();
exit(EXIT_FAILURE);
}
sudo_noreturn void
sudo_fatalx_nodebug_v1(const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(NULL, fmt, ap);
va_end(ap);
do_cleanup();
exit(EXIT_FAILURE);
}
sudo_noreturn void
sudo_vfatal_nodebug_v1(const char * restrict fmt, va_list ap)
{
warning(strerror(errno), fmt, ap);
do_cleanup();
exit(EXIT_FAILURE);
}
sudo_noreturn void
sudo_vfatalx_nodebug_v1(const char * restrict fmt, va_list ap)
{
warning(NULL, fmt, ap);
do_cleanup();
exit(EXIT_FAILURE);
}
void
sudo_warn_nodebug_v1(const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(strerror(errno), fmt, ap);
va_end(ap);
}
void
sudo_warnx_nodebug_v1(const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(NULL, fmt, ap);
va_end(ap);
}
void
sudo_vwarn_nodebug_v1(const char * restrict fmt, va_list ap)
{
warning(strerror(errno), fmt, ap);
}
void
sudo_vwarnx_nodebug_v1(const char * restrict fmt, va_list ap)
{
warning(NULL, fmt, ap);
}
sudo_noreturn void
sudo_gai_fatal_nodebug_v1(int errnum, const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(gai_strerror(errnum), fmt, ap);
va_end(ap);
do_cleanup();
exit(EXIT_FAILURE);
}
sudo_noreturn void
sudo_gai_vfatal_nodebug_v1(int errnum, const char * restrict fmt, va_list ap)
{
warning(gai_strerror(errnum), fmt, ap);
do_cleanup();
exit(EXIT_FAILURE);
}
void
sudo_gai_warn_nodebug_v1(int errnum, const char * restrict fmt, ...)
{
va_list ap;
va_start(ap, fmt);
warning(gai_strerror(errnum), fmt, ap);
va_end(ap);
}
void
sudo_gai_vwarn_nodebug_v1(int errnum, const char * restrict fmt, va_list ap)
{
warning(gai_strerror(errnum), fmt, ap);
}
static void
warning(const char * restrict errstr, const char * restrict fmt, va_list ap)
{
int cookie;
const int saved_errno = errno;
if (sudo_warn_setlocale != NULL)
sudo_warn_setlocale(false, &cookie);
if (sudo_warn_conversation != NULL) {
struct sudo_conv_message msgs[6];
char static_buf[1024], *buf = static_buf;
int nmsgs = 0;
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = getprogname();
if (fmt != NULL) {
va_list ap2;
int buflen;
va_copy(ap2, ap);
buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2);
va_end(ap2);
if (buflen >= ssizeof(static_buf)) {
if (vasprintf(&buf, fmt, ap) == -1)
buf = static_buf;
}
if (buflen > 0) {
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = ": ";
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = buf;
}
}
if (errstr != NULL) {
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = ": ";
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = errstr;
}
msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
msgs[nmsgs++].msg = "\n";
sudo_warn_conversation(nmsgs, msgs, NULL, NULL);
if (buf != static_buf)
free(buf);
} else {
fputs(getprogname(), stderr);
if (fmt != NULL) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
}
if (errstr != NULL) {
fputs(": ", stderr);
fputs(errstr, stderr);
}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (sudo_term_is_raw(fileno(stderr)))
putc('\r', stderr);
#endif
putc('\n', stderr);
}
if (sudo_warn_setlocale != NULL)
sudo_warn_setlocale(true, &cookie);
errno = saved_errno;
}
int
sudo_fatal_callback_register_v1(sudo_fatal_callback_t func)
{
struct sudo_fatal_callback *cb;
SLIST_FOREACH(cb, &callbacks, entries) {
if (func == cb->func)
return -1;
}
cb = malloc(sizeof(*cb));
if (cb == NULL)
return -1;
cb->func = func;
SLIST_INSERT_HEAD(&callbacks, cb, entries);
return 0;
}
int
sudo_fatal_callback_deregister_v1(sudo_fatal_callback_t func)
{
struct sudo_fatal_callback *cb, *prev = NULL;
SLIST_FOREACH(cb, &callbacks, entries) {
if (cb->func == func) {
if (prev == NULL)
SLIST_REMOVE_HEAD(&callbacks, entries);
else
SLIST_REMOVE_AFTER(prev, entries);
free(cb);
return 0;
}
prev = cb;
}
return -1;
}
void
sudo_warn_set_conversation_v1(sudo_conv_t conv)
{
sudo_warn_conversation = conv;
}
void
sudo_warn_set_locale_func_v1(sudo_warn_setlocale_t func)
{
sudo_warn_setlocale_prev = sudo_warn_setlocale;
sudo_warn_setlocale = func;
}
#ifdef HAVE_LIBINTL_H
char *
sudo_warn_gettext_v1(const char *domainname, const char *msgid)
{
int cookie;
char *msg;
if (sudo_warn_setlocale != NULL)
sudo_warn_setlocale(false, &cookie);
msg = dgettext(domainname, msgid);
if (sudo_warn_setlocale != NULL)
sudo_warn_setlocale(true, &cookie);
return msg;
}
#endif