view cinst/nssstore_linux.c @ 1065:5cf648c233d2

Note that quoted quotes are unhandled instead of FIXME We do not plan to fix that. Not supporting those is acceptable.
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 10 Sep 2014 11:52:55 +0200
parents 317ee9dc4684
children e210ecc32d69
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"
#include "util.h"

#define NSS_PROCESS_NAME "mozilla"


/**@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 uid of the user to install certificates for.
 * @param [in] gid 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[3] = {NULL, 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_install_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);

  if (g_debug)
    {
      argv[1] = "--debug";
    }

  ret = snprintf(argv[0], exe_path_len + 1, "%s%s", inst_dir, NSS_PROCESS_NAME);
  xfree (inst_dir);
  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();
  pid_t childprocess = -1;
  int status = -1;

  if (my_uid != 0)
    {
      /* Running as a user */
      char *homedir = getenv ("HOME");
      if (!homedir)
        {
          ERRORPRINTF ("Failed to find home directory\n");
        }

      /* Only one child for single user installation */
      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;
    }

  /* Start once as root to install in the system default directories. */
  childprocess = start_procces_for_user (to_install, to_remove,
                                         my_uid, getgid(), getenv ("HOME"));
  if (childprocess == -1)
    {
      ERRORPRINTF ("Failed to start default profile installation!\n");
      return -1;
    }

  /* Wait until the default profile directories are done. */
  childprocess = waitpid (childprocess, &status, 0);
  if (childprocess == -1 || !WIFEXITED(status))
    {
      ERRORPRINTF ("Child process did not finish.\n");
      return -1;
    }

  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/