packages feed

voyeur-0.1.0.0: libvoyeur/src/event.c

#ifdef __linux__
#define _GNU_SOURCE
#endif

#include <dlfcn.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef __linux__
#include <bsd/bsd.h>
#endif

#include "env.h"
#include "event.h"
#include "net.h"
#include "util.h"
#include <voyeur.h>

// Note that in event handlers, we always have to read the data,
// even if the callback isn't present, so we can move on to the
// next event in the stream. In practice the callback should always be
// present, so it's not worth worrying about.

static void handle_exec(voyeur_context* context, int fd)
{
  // Read the path.
  char* file;
  RETURN_ON_FAIL(voyeur_read_string, fd, &file, 0);

  // Read the arguments.
  int argc;
  RETURN_ON_FAIL(voyeur_read_int, fd, &argc);

  char** argv = malloc(sizeof(char*) * (argc + 1));
  for (int i = 0 ; i < argc ; ++i) {
    char* arg;
    RETURN_ON_FAIL(voyeur_read_string, fd, &arg, 0);
    argv[i] = arg;
  }
  argv[argc] = NULL;

  // Read the environment.
  int envc;
  char** envp = NULL;
  if (context->exec_opts & OBSERVE_EXEC_ENV) {
    RETURN_ON_FAIL(voyeur_read_int, fd, &envc);

    envp = malloc(sizeof(char*) * (envc + 1));
    for (int i = 0 ; i < envc ; ++i) {
      char* envvar;
      RETURN_ON_FAIL(voyeur_read_string, fd, &envvar, 0);
      envp[i] = envvar;
    }
    envp[envc] = NULL;
  }

  // Read the value of PATH.
  char* path = NULL;
  if (context->exec_opts & OBSERVE_EXEC_PATH) {
    RETURN_ON_FAIL(voyeur_read_string, fd, &path, 0);
  }

  // Read the current working directory.
  char* cwd = NULL;
  if (context->exec_opts & OBSERVE_EXEC_CWD) {
    RETURN_ON_FAIL(voyeur_read_string, fd, &cwd, 0);
  }

  // Read the pid and ppid.
  pid_t pid, ppid;
  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);
  RETURN_ON_FAIL(voyeur_read_pid, fd, &ppid);

  if (context->exec_cb) {
    ((voyeur_exec_callback)context->exec_cb)(file, argv, envp,
                                             path, cwd,
                                             pid, ppid,
                                             context->exec_userdata);
  }

  // Free everything.
  free(file);

  for (int i = 0 ; i < argc ; ++i) {
    free(argv[i]);
  }
  free(argv);

  if (context->exec_opts & OBSERVE_EXEC_ENV) {
    for (int i = 0 ; i < envc ; ++i) {
      free(envp[i]);
    }
    free(envp);
  }

  if (context->exec_opts & OBSERVE_EXEC_PATH) {
    free(path);
  }

  if (context->exec_opts & OBSERVE_EXEC_CWD) {
    free(cwd);
  }
}

static void handle_exit(voyeur_context* context, int fd)
{
  int status;
  pid_t pid, ppid;

  RETURN_ON_FAIL(voyeur_read_int, fd, &status);
  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);
  RETURN_ON_FAIL(voyeur_read_pid, fd, &ppid);

  if (context->exit_cb) {
    ((voyeur_exit_callback)context->exit_cb)(status, pid, ppid,
                                             context->exit_userdata);
  }
}

static void handle_open(voyeur_context* context, int fd)
{
  char* path;
  int oflag, mode, retval;
  char* cwd = NULL;
  pid_t pid;

  RETURN_ON_FAIL(voyeur_read_string, fd, &path, 0);
  RETURN_ON_FAIL(voyeur_read_int, fd, &oflag);
  RETURN_ON_FAIL(voyeur_read_int, fd, &mode);
  RETURN_ON_FAIL(voyeur_read_int, fd, &retval);

  if (context->open_opts & OBSERVE_OPEN_CWD) {
    RETURN_ON_FAIL(voyeur_read_string, fd, &cwd, 0);
  }

  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);

  if (context->open_cb) {
    ((voyeur_open_callback)context->open_cb)(path, oflag,
                                             (mode_t) mode,
                                             cwd, retval, pid,
                                             context->open_userdata);
  }

  free(path);
}

static void handle_close(voyeur_context* context, int fd)
{
  int fildes, retval;
  pid_t pid;

  RETURN_ON_FAIL(voyeur_read_int, fd, &fildes);
  RETURN_ON_FAIL(voyeur_read_int, fd, &retval);
  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);

  if (context->close_cb) {
    ((voyeur_close_callback)context->close_cb)(fildes, retval, pid,
                                               context->close_userdata);
  }
}

#define ON_EVENT(E, e)                          \
  case VOYEUR_EVENT_##E:                        \
    handle_##e(context, fd);                    \
    break;

void voyeur_handle_event(voyeur_context* context, voyeur_event_type type, int fd)
{
  switch (type) {
    MAP_EVENTS
    default:
      SHOULD_NOT_REACH("libvoyeur: got unknown event type %u\n",
                       (unsigned) type);
      return;
  }
}

#undef ON_EVENT

#ifdef __APPLE__
#define LIB_SUFFIX ".dylib"
#else
#define LIB_SUFFIX ".so"
#endif

#define ON_EVENT(_, e)           \
  "libvoyeur-" #e LIB_SUFFIX ":" \

static size_t compute_libs_size(size_t libdir_size)
{
  static char all_libs[] =
    MAP_EVENTS
    ;

  return sizeof(all_libs) + VOYEUR_EVENT_MAX * libdir_size;
}

#undef ON_EVENT

static char* get_default_resource_path(voyeur_context* context, bool* did_allocate)
{
  // We want absolute paths to the libraries, relative to the location of
  // libvoyeur. We use dladdr() to determine that location.
  *did_allocate = false;
  char* libdir = "./";
  Dl_info dlinfo;

  if (dladdr(voyeur_requested_libs, &dlinfo) && dlinfo.dli_fname) {
    // Strip the filename. It'd be great if 'dirname' could be used
    // for this, but it's not thread safe. Note that if no slash is
    // found, we just stick with "./".
    char* last_slash = strrchr(dlinfo.dli_fname, '/');
    if (last_slash) {
      *did_allocate = true;
      int len_with_slash_and_null = (last_slash - dlinfo.dli_fname) + 2;
      libdir = calloc(1, len_with_slash_and_null);
      strlcpy(libdir, dlinfo.dli_fname, len_with_slash_and_null);
    }
  }

  return libdir;
}

#define ON_EVENT(_, e)                                    \
  if (context->e##_cb) {                                  \
    if (prev) strlcat(libs, ":", libs_size);              \
    strlcat(libs, libdir, libs_size);                     \
    strlcat(libs, "libvoyeur-" #e LIB_SUFFIX, libs_size); \
    prev = 1;                                             \
  }                                                       \

char* voyeur_requested_libs(voyeur_context* context)
{
  bool did_allocate = false;
  char* libdir = context->resource_path
               ? context->resource_path
               : get_default_resource_path(context, &did_allocate);

  size_t libs_size = compute_libs_size(strnlen(libdir, 4096));
  char* libs = calloc(1, libs_size);
  char prev = 0;
  
  // If the user isn't observing exec events, we still want to apply libvoyeur
  // recursively, so we link in voyeur-exec in any case. (We set
  // OBSERVE_EXEC_SILENT so they won't get any events because of this.)
  char did_force_exec = 0;
  if (!context->exec_cb) {
    did_force_exec = 1;
    context->exec_cb = &did_force_exec;  // Just a dummy value.
  }

  MAP_EVENTS

  if (did_force_exec) {
    context->exec_cb = NULL;
  }

  if (did_allocate) {
    free(libdir);
  }

  return libs;
}

#undef ON_EVENT

#define ON_EVENT(E, e) opts[VOYEUR_EVENT_##E] = voyeur_encode_options(context->e##_opts);

char* voyeur_requested_opts(voyeur_context* context)
{
  // VOYEUR_EVENT_MAX is one greater than the highest event number and
  // thus leaves room for a terminating null.
  char* opts = calloc(1, VOYEUR_EVENT_MAX);

  // If the user isn't observing exec events, we still want to apply libvoyeur
  // recursively, so we link in voyeur-exec in any case. (We set
  // OBSERVE_EXEC_SILENT so they won't get any events because of this.)
  if (!context->exec_cb) {
    context->exec_opts = OBSERVE_EXEC_SILENT;
  }

  MAP_EVENTS

  return opts;
}

#undef ON_EVENT

#define ON_EVENT(_, e)                                  \
void voyeur_observe_##e(voyeur_context_t ctx,           \
                        uint8_t opts,                   \
                        voyeur_##e##_callback callback, \
                        void* userdata)                 \
{                                                       \
  voyeur_context* context = (voyeur_context*) ctx;      \
  context->e##_opts = opts;                             \
  context->e##_cb = (void*) callback;                   \
  context->e##_userdata = userdata;                     \
}

MAP_EVENTS

#undef ON_EVENT