"Fossies" - the Fresh Open Source Software Archive

Member "sudo-1.9.11p3/plugins/audit_json/audit_json.c" (12 Jun 2022, 19932 Bytes) of package /linux/misc/sudo-1.9.11p3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "audit_json.c" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * SPDX-License-Identifier: ISC
    3  *
    4  * Copyright (c) 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
    5  *
    6  * Permission to use, copy, modify, and distribute this software for any
    7  * purpose with or without fee is hereby granted, provided that the above
    8  * copyright notice and this permission notice appear in all copies.
    9  *
   10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   17  */
   18 
   19 /*
   20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
   21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
   22  */
   23 
   24 #include <config.h>
   25 
   26 #include <sys/stat.h>
   27 #include <sys/wait.h>
   28 
   29 #include <stdio.h>
   30 #include <stdlib.h>
   31 #ifdef HAVE_STDBOOL_H
   32 # include <stdbool.h>
   33 #else
   34 # include "compat/stdbool.h"
   35 #endif /* HAVE_STDBOOL_H */
   36 #include <string.h>
   37 #include <signal.h>
   38 #include <unistd.h>
   39 #include <fcntl.h>
   40 #include <limits.h>
   41 #include <time.h>
   42 
   43 #include "pathnames.h"
   44 #include "sudo_compat.h"
   45 #include "sudo_conf.h"
   46 #include "sudo_debug.h"
   47 #include "sudo_dso.h"
   48 #include "sudo_fatal.h"
   49 #include "sudo_gettext.h"
   50 #include "sudo_json.h"
   51 #include "sudo_plugin.h"
   52 #include "sudo_util.h"
   53 
   54 static int audit_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
   55 static sudo_conv_t audit_conv;
   56 static sudo_printf_t audit_printf;
   57 
   58 static struct audit_state {
   59     int submit_optind;
   60     char uuid_str[37];
   61     bool accepted;
   62     FILE *log_fp;
   63     char *logfile;
   64     char * const * settings;
   65     char * const * user_info;
   66     char * const * submit_argv;
   67     char * const * submit_envp;
   68 } state = { -1 };
   69 
   70 /* Filter out entries in settings[] that are not really options. */
   71 char * const settings_filter[] = {
   72     "debug_flags",
   73     "max_groups",
   74     "network_addrs",
   75     "plugin_dir",
   76     "plugin_path",
   77     "progname",
   78     NULL
   79 };
   80 
   81 static int
   82 audit_json_open(unsigned int version, sudo_conv_t conversation,
   83     sudo_printf_t plugin_printf, char * const settings[],
   84     char * const user_info[], int submit_optind, char * const submit_argv[],
   85     char * const submit_envp[], char * const plugin_options[],
   86     const char **errstr)
   87 {
   88     struct sudo_conf_debug_file_list debug_files =
   89     TAILQ_HEAD_INITIALIZER(debug_files);
   90     struct sudo_debug_file *debug_file;
   91     const char *cp, *plugin_path = NULL;
   92     unsigned char uuid[16];
   93     char * const *cur;
   94     mode_t oldmask;
   95     int fd, ret = -1;
   96     debug_decl_vars(audit_json_open, SUDO_DEBUG_PLUGIN);
   97 
   98     audit_conv = conversation;
   99     audit_printf = plugin_printf;
  100 
  101     /*
  102      * Stash initial values.
  103      */
  104     state.submit_optind = submit_optind;
  105     state.settings = settings;
  106     state.user_info = user_info;
  107     state.submit_argv = submit_argv;
  108     state.submit_envp = submit_envp;
  109 
  110     /* Initialize the debug subsystem.  */
  111     for (cur = settings; (cp = *cur) != NULL; cur++) {
  112         if (strncmp(cp, "debug_flags=", sizeof("debug_flags=") - 1) == 0) {
  113             cp += sizeof("debug_flags=") - 1;
  114             if (sudo_debug_parse_flags(&debug_files, cp) == -1)
  115                 goto oom;
  116             continue;
  117         }
  118         if (strncmp(cp, "plugin_path=", sizeof("plugin_path=") - 1) == 0) {
  119             plugin_path = cp + sizeof("plugin_path=") - 1;
  120             continue;
  121         }
  122     }
  123     if (plugin_path != NULL && !TAILQ_EMPTY(&debug_files)) {
  124     audit_debug_instance =
  125         sudo_debug_register(plugin_path, NULL, NULL, &debug_files, -1);
  126     if (audit_debug_instance == SUDO_DEBUG_INSTANCE_ERROR) {
  127         *errstr = U_("unable to initialize debugging");
  128         goto bad;
  129     }
  130     sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys);
  131     }
  132 
  133     /* Create a UUID for this command for use with audit records. */
  134     sudo_uuid_create(uuid);
  135     if (sudo_uuid_to_string(uuid, state.uuid_str, sizeof(state.uuid_str)) == NULL) {
  136     *errstr = U_("unable to generate UUID");
  137     goto bad;
  138     }
  139 
  140     /* Parse plugin_options to check for logfile option. */
  141     if (plugin_options != NULL) {
  142     for (cur = plugin_options; (cp = *cur) != NULL; cur++) {
  143         if (strncmp(cp, "logfile=", sizeof("logfile=") - 1) == 0) {
  144         state.logfile = strdup(cp + sizeof("logfile=") - 1);
  145         if (state.logfile == NULL)
  146             goto oom;
  147         }
  148     }
  149     }
  150     if (state.logfile == NULL) {
  151     if (asprintf(&state.logfile, "%s/sudo_audit.json", _PATH_SUDO_LOGDIR) == -1)
  152         goto oom;
  153     }
  154 
  155     /* open log file */
  156     /* TODO: support pipe */
  157     oldmask = umask(S_IRWXG|S_IRWXO);
  158     fd = open(state.logfile, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
  159     (void)umask(oldmask);
  160     if (fd == -1 || (state.log_fp = fdopen(fd, "w")) == NULL) {
  161     *errstr = U_("unable to open audit system");
  162     if (fd != -1)
  163         close(fd);
  164     goto bad;
  165     }
  166 
  167     ret = 1;
  168     goto done;
  169 
  170 oom:
  171     sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
  172     *errstr = U_("unable to allocate memory");
  173 
  174 bad:
  175     if (state.log_fp != NULL) {
  176     fclose(state.log_fp);
  177     state.log_fp = NULL;
  178     }
  179 
  180 done:
  181     while ((debug_file = TAILQ_FIRST(&debug_files))) {
  182     TAILQ_REMOVE(&debug_files, debug_file, entries);
  183     free(debug_file->debug_file);
  184     free(debug_file->debug_flags);
  185     free(debug_file);
  186     }
  187 
  188     debug_return_int(ret);
  189 }
  190 
  191 static bool
  192 add_key_value(struct json_container *json, const char *str)
  193 {
  194     struct json_value json_value;
  195     const char *cp, *errstr;
  196     char name[256];
  197     size_t len;
  198     debug_decl(add_key_value, SUDO_DEBUG_PLUGIN);
  199 
  200     if ((cp = strchr(str, '=')) == NULL) {
  201     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
  202         "ignoring bad command info string \"%s\"", str);
  203     debug_return_bool(false);
  204     }
  205     len = (size_t)(cp - str);
  206     cp++;
  207 
  208     /* Variable name currently limited to 256 chars */
  209     if (len >= sizeof(name)) {
  210     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
  211         "ignoring long command info name \"%.*s\"", (int)len, str);
  212     debug_return_bool(false);
  213     }
  214     memcpy(name, str, len);
  215     name[len] = '\0';
  216 
  217     /* Check for bool or number. */
  218     json_value.type = JSON_NULL;
  219     switch (cp[0]) {
  220     case '0':
  221     if (cp[1] == '\0') {
  222         /* Only treat a plain "0" as number 0. */
  223         json_value.u.number = 0;
  224         json_value.type = JSON_NUMBER;
  225     }
  226     break;
  227     case '+': case '-':
  228     if (cp[1] == '0') {
  229         /* Encode octal numbers as strings. */
  230         break;
  231     }
  232     FALLTHROUGH;
  233     case '1': case '2': case '3': case '4': case '5':
  234     case '6': case '7': case '8': case '9':
  235     json_value.u.number = sudo_strtonum(cp, INT_MIN, INT_MAX, &errstr);
  236     if (errstr == NULL)
  237         json_value.type = JSON_NUMBER;
  238     break;
  239     case 't':
  240     if (strcmp(cp, "true") == 0) {
  241         json_value.type = JSON_BOOL;
  242         json_value.u.boolean = true;
  243     }
  244     break;
  245     case 'f':
  246     if (strcmp(cp, "false") == 0) {
  247         json_value.type = JSON_BOOL;
  248         json_value.u.boolean = false;
  249     }
  250     break;
  251     }
  252 
  253     /* Default to string type. */
  254     if (json_value.type == JSON_NULL) {
  255     json_value.type = JSON_STRING;
  256     json_value.u.string = cp;
  257     }
  258 
  259     debug_return_bool(sudo_json_add_value(json, name, &json_value));
  260 }
  261 
  262 static bool
  263 add_array(struct json_container *json, const char *name, char * const * array)
  264 {
  265     const char *cp;
  266     struct json_value json_value;
  267     debug_decl(add_array, SUDO_DEBUG_PLUGIN);
  268 
  269     if (!sudo_json_open_array(json, name))
  270     debug_return_bool(false);
  271     while ((cp = *array) != NULL) {
  272     json_value.type = JSON_STRING;
  273     json_value.u.string = cp;
  274     if (!sudo_json_add_value(json, name, &json_value))
  275         debug_return_bool(false);
  276     array++;
  277     }
  278     if (!sudo_json_close_array(json))
  279     debug_return_bool(false);
  280 
  281     debug_return_bool(true);
  282 }
  283 
  284 static bool
  285 filter_key_value(const char *kv, char * const * filter)
  286 {
  287     char * const *cur;
  288     const char *cp;
  289     size_t namelen;
  290 
  291     if (filter != NULL) {
  292     namelen = strcspn(kv, "=");
  293     for (cur = filter; (cp = *cur) != NULL; cur++) {
  294         if (strncmp(kv, cp, namelen) == 0 && cp[namelen] == '\0')
  295         return true;
  296     }
  297     }
  298     return false;
  299 }
  300 
  301 static bool
  302 add_key_value_object(struct json_container *json, const char *name,
  303     char * const * array, char * const * filter)
  304 {
  305     char * const *cur;
  306     const char *cp;
  307     bool empty = false;
  308     debug_decl(add_key_value_object, SUDO_DEBUG_PLUGIN);
  309 
  310     if (filter != NULL) {
  311     /* Avoid printing an empty object if everything is filtered. */
  312     empty = true;
  313     for (cur = array; (cp = *cur) != NULL; cur++) {
  314         if (!filter_key_value(cp, filter)) {
  315         empty = false;
  316         break;
  317         }
  318     }
  319     }
  320     if (!empty) {
  321     if (!sudo_json_open_object(json, name))
  322         goto bad;
  323     for (cur = array; (cp = *cur) != NULL; cur++) {
  324         if (filter_key_value(cp, filter))
  325         continue;
  326         if (!add_key_value(json, cp))
  327         goto bad;
  328     }
  329     if (!sudo_json_close_object(json))
  330         goto bad;
  331     }
  332 
  333     debug_return_bool(true);
  334 bad:
  335     debug_return_bool(false);
  336 }
  337 
  338 static bool
  339 add_timestamp(struct json_container *json, struct timespec *ts)
  340 {
  341     struct json_value json_value;
  342     time_t secs = ts->tv_sec;
  343     char timebuf[1024];
  344     struct tm gmt;
  345     int len;
  346     debug_decl(add_timestamp, SUDO_DEBUG_PLUGIN);
  347 
  348     if (gmtime_r(&secs, &gmt) == NULL)
  349     debug_return_bool(false);
  350 
  351     sudo_json_open_object(json, "timestamp");
  352 
  353     json_value.type = JSON_NUMBER;
  354     json_value.u.number = ts->tv_sec;
  355     sudo_json_add_value(json, "seconds", &json_value);
  356 
  357     json_value.type = JSON_NUMBER;
  358     json_value.u.number = ts->tv_nsec;
  359     sudo_json_add_value(json, "nanoseconds", &json_value);
  360 
  361     timebuf[sizeof(timebuf) - 1] = '\0';
  362     len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
  363     if (len != 0 && timebuf[sizeof(timebuf) - 1] == '\0'){
  364     json_value.type = JSON_STRING;
  365     json_value.u.string = timebuf;
  366     sudo_json_add_value(json, "iso8601", &json_value);
  367     }
  368 
  369     timebuf[sizeof(timebuf) - 1] = '\0';
  370     len = strftime(timebuf, sizeof(timebuf), "%a %b %e %H:%M:%S %Z %Y", &gmt);
  371     if (len != 0 && timebuf[sizeof(timebuf) - 1] == '\0'){
  372     json_value.type = JSON_STRING;
  373     json_value.u.string = timebuf;
  374     sudo_json_add_value(json, "localtime", &json_value);
  375     }
  376 
  377     sudo_json_close_object(json);
  378 
  379     debug_return_bool(true);
  380 }
  381 
  382 static int
  383 audit_write_json(struct json_container *json)
  384 {
  385     struct stat sb;
  386     int ret = -1;
  387     debug_decl(audit_write_json, SUDO_DEBUG_PLUGIN);
  388 
  389     if (!sudo_lock_file(fileno(state.log_fp), SUDO_LOCK)) {
  390     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
  391         "unable to lock %s", state.logfile);
  392     goto done;
  393     }
  394 
  395     /* Note: assumes file ends in "\n}\n" */
  396     if (fstat(fileno(state.log_fp), &sb) == -1) {
  397     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
  398         "unable to stat %s", state.logfile);
  399     goto done;
  400     }
  401     if (sb.st_size == 0) {
  402     /* New file */
  403     putc('{', state.log_fp);
  404     } else if (fseeko(state.log_fp, -3, SEEK_END) == 0) {
  405     /* Continue file, overwrite the final "\n}\n" */
  406     putc(',', state.log_fp);
  407     } else {
  408     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
  409         "unable to seek %s", state.logfile);
  410     goto done;
  411     }
  412 
  413     fputs(sudo_json_get_buf(json), state.log_fp);
  414     fputs("\n}\n", state.log_fp);
  415     fflush(state.log_fp);
  416     (void)sudo_lock_file(fileno(state.log_fp), SUDO_UNLOCK);
  417 
  418     /* TODO: undo partial record on error */
  419     if (!ferror(state.log_fp))
  420     ret = true;
  421 
  422 done:
  423     debug_return_int(ret);
  424 }
  425 
  426 static int
  427 audit_write_exit_record(int exit_status, int error)
  428 {
  429     struct json_container json;
  430     struct json_value json_value;
  431     struct timespec now;
  432     int ret = -1;
  433     debug_decl(audit_write_exit_record, SUDO_DEBUG_PLUGIN);
  434 
  435     if (sudo_gettime_real(&now) == -1) {
  436     sudo_warn("%s", U_("unable to read the clock"));
  437     goto done;
  438     }
  439 
  440     if (!sudo_json_init(&json, 4, false, false))
  441     goto oom;
  442     if (!sudo_json_open_object(&json, "exit"))
  443     goto oom;
  444 
  445     /* Write UUID */
  446     json_value.type = JSON_STRING;
  447     json_value.u.string = state.uuid_str;
  448     if (!sudo_json_add_value(&json, "uuid", &json_value))
  449     goto oom;
  450 
  451     /* Write time stamp */
  452     if (!add_timestamp(&json, &now))
  453     goto oom;
  454 
  455     if (error != 0) {
  456     /* Error executing command */
  457     json_value.type = JSON_STRING;
  458     json_value.u.string = strerror(error);
  459     if (!sudo_json_add_value(&json, "error", &json_value))
  460         goto oom;
  461     } else {
  462         if (WIFEXITED(exit_status)) {
  463         /* Command exited normally. */
  464         json_value.type = JSON_NUMBER;
  465         json_value.u.number = WEXITSTATUS(exit_status);
  466         if (!sudo_json_add_value(&json, "exit_value", &json_value))
  467         goto oom;
  468         } else if (WIFSIGNALED(exit_status)) {
  469         /* Command killed by signal. */
  470         char signame[SIG2STR_MAX];
  471             int signo = WTERMSIG(exit_status);
  472             if (signo <= 0 || sig2str(signo, signame) == -1) {
  473         json_value.type = JSON_NUMBER;
  474         json_value.u.number = signo;
  475         if (!sudo_json_add_value(&json, "signal", &json_value))
  476             goto oom;
  477             } else {
  478         json_value.type = JSON_STRING;
  479         json_value.u.string = signame; // -V507
  480         if (!sudo_json_add_value(&json, "signal", &json_value))
  481             goto oom;
  482         }
  483         /* Core dump? */
  484         json_value.type = JSON_BOOL;
  485         json_value.u.boolean = WCOREDUMP(exit_status);
  486         if (!sudo_json_add_value(&json, "dumped_core", &json_value))
  487         goto oom;
  488         /* Exit value */
  489         json_value.type = JSON_NUMBER;
  490         json_value.u.number = WTERMSIG(exit_status) | 128;
  491         if (!sudo_json_add_value(&json, "exit_value", &json_value))
  492         goto oom;
  493         }
  494     }
  495 
  496     if (!sudo_json_close_object(&json))
  497     goto oom;
  498 
  499     ret = audit_write_json(&json);
  500     sudo_json_free(&json);
  501 done:
  502     debug_return_int(ret);
  503 oom:
  504     sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
  505     sudo_json_free(&json);
  506     debug_return_int(-1);
  507 }
  508 
  509 static int
  510 audit_write_record(const char *audit_str, const char *plugin_name,
  511     unsigned int plugin_type, const char *reason, char * const command_info[],
  512     char * const run_argv[], char * const run_envp[])
  513 {
  514     struct json_container json;
  515     struct json_value json_value;
  516     struct timespec now;
  517     int ret = -1;
  518     debug_decl(audit_write_record, SUDO_DEBUG_PLUGIN);
  519 
  520     if (sudo_gettime_real(&now) == -1) {
  521     sudo_warn("%s", U_("unable to read the clock"));
  522     goto done;
  523     }
  524 
  525     if (!sudo_json_init(&json, 4, false, false))
  526     goto oom;
  527     if (!sudo_json_open_object(&json, audit_str))
  528     goto oom;
  529 
  530     json_value.type = JSON_STRING;
  531     json_value.u.string = plugin_name;
  532     if (!sudo_json_add_value(&json, "plugin_name", &json_value))
  533     goto oom;
  534 
  535     switch (plugin_type) {
  536     case SUDO_FRONT_END:
  537     json_value.u.string = "front-end";
  538     break;
  539     case SUDO_POLICY_PLUGIN:
  540     json_value.u.string = "policy";
  541     break;
  542     case SUDO_IO_PLUGIN:
  543     json_value.u.string = "io";
  544     break;
  545     case SUDO_APPROVAL_PLUGIN:
  546     json_value.u.string = "approval";
  547     break;
  548     case SUDO_AUDIT_PLUGIN:
  549     json_value.u.string = "audit";
  550     break;
  551     default:
  552     json_value.u.string = "unknown";
  553     break;
  554     }
  555     json_value.type = JSON_STRING;
  556     if (!sudo_json_add_value(&json, "plugin_type", &json_value))
  557     goto oom;
  558 
  559     /* error and reject audit events usually contain a reason. */
  560     if (reason != NULL) {
  561     json_value.type = JSON_STRING;
  562     json_value.u.string = reason;
  563     if (!sudo_json_add_value(&json, "reason", &json_value))
  564         goto oom;
  565     }
  566 
  567     json_value.type = JSON_STRING;
  568     json_value.u.string = state.uuid_str;
  569     if (!sudo_json_add_value(&json, "uuid", &json_value))
  570     goto oom;
  571 
  572     if (!add_timestamp(&json, &now))
  573     goto oom;
  574 
  575     /* Write key=value objects. */
  576     if (state.settings != NULL) {
  577     if (!add_key_value_object(&json, "options", state.settings, settings_filter))
  578         goto oom;
  579     } else {
  580     sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
  581         "missing settings list");
  582     }
  583     if (state.user_info != NULL) {
  584     if (!add_key_value_object(&json, "user_info", state.user_info, NULL))
  585         goto oom;
  586     } else {
  587     sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
  588         "missing user_info list");
  589     }
  590     if (command_info != NULL) {
  591     if (!add_key_value_object(&json, "command_info", command_info, NULL))
  592         goto oom;
  593     }
  594 
  595     /* Write submit_optind before submit_argv */
  596     json_value.type = JSON_NUMBER;
  597     json_value.u.number = state.submit_optind;
  598     if (!sudo_json_add_value(&json, "submit_optind", &json_value))
  599     goto oom;
  600 
  601     if (state.submit_argv != NULL) {
  602     if (!add_array(&json, "submit_argv", state.submit_argv))
  603         goto oom;
  604     } else {
  605     sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
  606         "missing submit_argv array");
  607     }
  608     if (state.submit_envp != NULL) {
  609     if (!add_array(&json, "submit_envp", state.submit_envp))
  610         goto oom;
  611     } else {
  612     sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
  613         "missing submit_envp array");
  614     }
  615     if (run_argv != NULL) {
  616     if (!add_array(&json, "run_argv", run_argv))
  617         goto oom;
  618     }
  619     if (run_envp != NULL) {
  620     if (!add_array(&json, "run_envp", run_envp))
  621         goto oom;
  622     }
  623 
  624     if (!sudo_json_close_object(&json))
  625     goto oom;
  626 
  627     ret = audit_write_json(&json);
  628     sudo_json_free(&json);
  629 
  630 done:
  631     debug_return_int(ret);
  632 oom:
  633     sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
  634     sudo_json_free(&json);
  635     debug_return_int(-1);
  636 }
  637 
  638 static int
  639 audit_json_accept(const char *plugin_name, unsigned int plugin_type,
  640     char * const command_info[], char * const run_argv[],
  641     char * const run_envp[], const char **errstr)
  642 {
  643     int ret;
  644     debug_decl(audit_json_accept, SUDO_DEBUG_PLUGIN);
  645 
  646     /* Ignore the extra accept event from the sudo front-end. */
  647     if (plugin_type == SUDO_FRONT_END)
  648     debug_return_int(true);
  649 
  650     state.accepted = true;
  651 
  652     ret = audit_write_record("accept", plugin_name, plugin_type, NULL,
  653     command_info, run_argv, run_envp);
  654 
  655     debug_return_int(ret);
  656 }
  657 
  658 static int
  659 audit_json_reject(const char *plugin_name, unsigned int plugin_type,
  660     const char *reason, char * const command_info[], const char **errstr)
  661 {
  662     int ret;
  663     debug_decl(audit_json_reject, SUDO_DEBUG_PLUGIN);
  664 
  665     ret = audit_write_record("reject", plugin_name, plugin_type,
  666     reason, command_info, NULL, NULL);
  667 
  668     debug_return_int(ret);
  669 }
  670 
  671 static int
  672 audit_json_error(const char *plugin_name, unsigned int plugin_type,
  673     const char *reason, char * const command_info[], const char **errstr)
  674 {
  675     int ret;
  676     debug_decl(audit_json_error, SUDO_DEBUG_PLUGIN);
  677 
  678     ret = audit_write_record("error", plugin_name, plugin_type,
  679     reason, command_info, NULL, NULL);
  680 
  681     debug_return_int(ret);
  682 }
  683 
  684 static void
  685 audit_json_close(int status_type, int status)
  686 {
  687     debug_decl(audit_json_close, SUDO_DEBUG_PLUGIN);
  688 
  689     switch (status_type) {
  690     case SUDO_PLUGIN_NO_STATUS:
  691     break;
  692     case SUDO_PLUGIN_WAIT_STATUS:
  693     audit_write_exit_record(status, 0);
  694     break;
  695     case SUDO_PLUGIN_EXEC_ERROR:
  696     audit_write_exit_record(0, status);
  697     break;
  698     case SUDO_PLUGIN_SUDO_ERROR:
  699     audit_write_record("error", "sudo", 0, strerror(status),
  700         NULL, NULL, NULL);
  701     break;
  702     default:
  703     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
  704         "unexpected status type %d, value %d", status_type, status);
  705     break;
  706     }
  707 
  708     free(state.logfile);
  709     if (state.log_fp != NULL)
  710     fclose(state.log_fp);
  711 
  712     debug_return;
  713 }
  714 
  715 static int
  716 audit_json_show_version(int verbose)
  717 {
  718     debug_decl(audit_json_show_version, SUDO_DEBUG_PLUGIN);
  719 
  720     audit_printf(SUDO_CONV_INFO_MSG, "JSON audit plugin version %s\n",
  721         PACKAGE_VERSION);
  722 
  723     debug_return_int(true);
  724 }
  725 
  726 sudo_dso_public struct audit_plugin audit_json = {
  727     SUDO_AUDIT_PLUGIN,
  728     SUDO_API_VERSION,
  729     audit_json_open,
  730     audit_json_close,
  731     audit_json_accept,
  732     audit_json_reject,
  733     audit_json_error,
  734     audit_json_show_version,
  735     NULL, /* register_hooks */
  736     NULL /* deregister_hooks */
  737 };