view cinst/nssstore_linux.c @ 681:5e25159d7025

Merged
author Sascha Wilde <wilde@intevation.de>
date Mon, 30 Jun 2014 18:35:22 +0200
parents e41a2537b84d
children 216a65d7fc4b
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#ifndef WIN32

/* @file
   @brief Linux implementation of nssstore process control.
*/

#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <pwd.h>

#include "nssstore.h"
#include "logging.h"
#include "strhelp.h"

#define NSS_PROCESS_NAME "mozilla"

/**@brief get the current path of the executable
 *
 * Looks up the current executables directory. The caller
 * has to free the return value.
 *
 * The returned value includes the last /
 *
 * @returns the absolute directory of the currently executed executable or NULL
 */
char *
get_exe_dir()
{
  char *retval = NULL,
       *p = NULL,
       buf[PATH_MAX];
  ssize_t ret;
  size_t path_len = 0;

  ret = readlink ("/proc/self/exe", buf, PATH_MAX);
  if (ret <= 0)
    {
      ERRORPRINTF ("readlink failed\n");
      return NULL;
    }

  buf[ret] = '\0';

  /* cut off the filename */
  p = strrchr (buf, '/');
  if (p == NULL)
    {
      ERRORPRINTF ("No filename found.\n");
      return NULL;
    }
  *(p + 1) = '\0';

  path_len = strlen (buf);
  retval = xmalloc (path_len + 1);
  strncpy (retval, buf, path_len);
  retval[path_len] = '\0';

  return retval;
}

/**@brief Start the process to install / remove
 *
 * This forks the process and executes the NSS installation
 * process. It also writes the Instructions to that process.
 *
 * @param [in] to_install strv of DER encoded certificates to be added.
 * @param [in] to_remove strv of DER encoded certificates to be remvoed.
 * @param [in] uid_t uid of the user to install certificates for.
 * @param [in] gid_t the gid of the user to install certificates for.
 * @param [in] homedir the homedir of the user.
 *
 * @returns childs pid on success. -1 on failure
 */
static int
start_procces_for_user (char **to_install, char **to_remove,
                        uid_t uid, gid_t gid, char *homedir)
{
  int pipe_fd[2];
  pid_t pid = 0;
  char *argv[2] = {NULL, NULL},
       *envp[2] = {NULL, NULL},
       *inst_dir = NULL;
  size_t homedir_len = 0,
         exe_path_len = 0;
  int ret = -1,
      i = 0;
  FILE *stream = NULL;
  bool success = false;

  if (homedir == NULL)
    {
      ERRORPRINTF ("Invalid call to start_process_for_user\n");
      return -1;
    }

  homedir_len = strlen (homedir);

  /* Allocate space for HOME=homedir\0 */
  envp[0] = xmalloc (homedir_len + 6);
  envp[1] = NULL;

  ret = snprintf (envp[0], homedir_len + 6, "HOME=%s", homedir);

  if (ret < 0 || (size_t) ret != homedir_len + 5)
    {
      ERRORPRINTF ("Error setting home env variable.\n");
      xfree (envp[0]);
      return -1;
    }

  /* Set up the file name of the installer process */
  inst_dir = get_exe_dir();
  if (inst_dir == NULL)
    {
      ERRORPRINTF ("Failed to find installation directory.\n");
      xfree (envp[0]);
      return -1;
    }

  exe_path_len = strlen(inst_dir) + strlen(NSS_PROCESS_NAME);
  argv[0] = xmalloc (exe_path_len + 1);

  ret = snprintf(argv[0], exe_path_len + 1, "%s%s", inst_dir, NSS_PROCESS_NAME);
  if (ret < 0 || (size_t) ret != exe_path_len)
    {
      ERRORPRINTF ("Error setting executable variable.\n");
      xfree (argv[0]);
      return -1;
    }

  if (pipe (pipe_fd))
    {
      ERRORPRINTF ("Failed to create pipe.\n");
      return -1;
    }

  pid = fork();

  if (pid == (pid_t) -1)
    {
      ERRORPRINTF ("Failed to fork child.\n");
      return -1;
    }

  if (pid == (pid_t) 0)
    {
      /* Drop privileges */
      if (setgid (gid) || setuid (uid))
        {
          syslog_error_printf("Failed to drop privileges: %s", strerror(errno));
          exit(-1);
        }

      close (pipe_fd[1]);
      dup2 (pipe_fd[0], 0);
      close (pipe_fd[0]);
      execve (argv[0], argv, envp);
      exit (127);
    }

  close (pipe_fd[0]);
  stream = fdopen(pipe_fd[1], "w");
  if (stream == NULL)
    {
      ERRORPRINTF ("Failed to open pipe for writing\n");
      goto done;
    }

  /* The NSS installer may exit on error before we are done
   * telling it what to do. We want to handle that rather
   * then die unexpectedly. */
  signal(SIGPIPE, SIG_IGN);

  /* Send the instructions */
  for (i = 0; to_install && to_install[i]; i++)
    {
      if (fprintf (stream, "I:%s\n", to_install[i]) <= 3)
        {
          int err = errno;
          ERRORPRINTF ("Write failed: %s \n", strerror(err));
          if (err == 32)
            {
              /* Broken pipe is expected if there are no NSS stores
                 to be found the process just exits. That's ok */
              success = true;
            }
          goto done;
        }
    }

  for (i = 0; to_remove && to_remove[i]; i++)
    {
      if (fprintf (stream, "R:%s\n", to_remove[i]) <= 3)
        {
          int err = errno;
          ERRORPRINTF ("Write failed: %s \n", strerror(err));
          if (err == 32)
            {
              /* Broken pipe is expected if there are no NSS stores
                 to be found the process just exits. That's ok */
              success = true;
            }
          goto done;
        }
    }

  success = true;

done:
  if (stream) {
    fclose (stream);
  }
  xfree (argv[0]);
  xfree (envp[0]);
  close (pipe_fd[0]);
  close (pipe_fd[1]);

  if (success)
    {
      return pid;
    }
  return -1;
}

int
write_stores_nss (char **to_install, char **to_remove)
{
  struct passwd *usr_it = NULL;
  uid_t my_uid = geteuid();

  if (my_uid != 0)
    {
      /* Running as a user */
      char *homedir = getenv ("HOME");
      pid_t childprocess = -1; /* Only one child for single user installation */
      int status = -1;
      if (!homedir)
        {
          ERRORPRINTF ("Failed to find home directory\n");
        }

      childprocess = start_procces_for_user (to_install, to_remove,
                                             my_uid, getgid(), homedir);

      if (childprocess == -1)
        {
          ERRORPRINTF ("Failed to start childprocess!\n");
          return -1;
        }

      childprocess = waitpid (childprocess, &status, 0);
      if (childprocess == -1 || !WIFEXITED(status))
        {
          ERRORPRINTF ("Waitpid failed.\n");
          return -1;
        }

      return 0;
    }

  setpwent();

  while ((usr_it = getpwent ()) != NULL)
    {
      /* Skip obvious system accounts */
      if (strcmp(usr_it->pw_shell, "/usr/sbin/nologin") == 0 ||
          strcmp(usr_it->pw_shell, "/bin/false") == 0)
        {
          continue;
        }
      /* A check if the home directory starts with /home might be
         appropiate */
      start_procces_for_user (to_install,
                              to_remove,
                              usr_it->pw_uid,
                              usr_it->pw_gid,
                              usr_it->pw_dir);

    }

  endpwent();

  waitpid (-1, NULL, 0);

  DEBUGPRINTF ("NSS installation done\n");
  return 0;
}
#endif

http://wald.intevation.org/projects/trustbridge/