view cinst/mozilla.c @ 249:6a7eb102716d

Remove code duplication by unifying the certificatelist. You should now check for isInstallCert to determine wether this certificate should be installed or removed. Leaving the getInstallCertificates and getRemoveCertificates in place for compatibilty would have been easier to keep the tests stable.
author Andre Heinecke <aheinecke@intevation.de>
date Mon, 31 Mar 2014 08:06:17 +0000
parents 1efe494c3d2b
children bd7fb50078b4
line wrap: on
line source
/**
 * @file
 * @brief Mozilla installation process
 *
 * Reads from stdin a list of instructions in the form:
 *
 * I:<base64 DER econded certificate>
 * R:<base64 DER econded certificate>
 * ...
 *
 * With one instruction per line. the maximum size of an input
 * line is 9999 characters (including the \r\n) at the end of the line.
 *
 * Certificates marked with I: will be installed and the ones
 * marked with R: will be searched and if available removed from
 * the databases.
 *
 * This tool tries to find all NSS databases the user has
 * access to and to execute the instructions on all of them.
 *
 * If there are other processes accessing the databases the caller
 * has to ensure that those are terminated before this process is
 * executed.
 *
 * If the same certificate is marked to be installed and to be removed
 * in one call the behavior is undefined. This should be avoided and
 * may lead to errors.
 *
 * Returns 0 on success (Even when no stores where found) an error value
 * as defined in errorcodes.h otherwise.
 *
 * Success messages are written to stdout. Errors to stderr. For logging
 * purposes each installation / removal of a certificate will be reported
 * with the profile name that it modified.
 *
 */

/**
 * @brief Needs to eb defined to get strnlen()
 */
#define _POSIX_C_SOURCE 200809L

/* REMOVEME: */
#include <unistd.h>

#include <dirent.h>
#include <cert.h>
#include <certt.h>
#include <nss.h>
#include <pk11pub.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#define DEBUGPREFIX "MOZ-"
#include "debug.h"

#include "errorcodes.h"
#include "portpath.h"
#include "strhelp.h"
#include "nss-secitemlist.h"

#ifndef _WIN32
#define CONFDIRS ".mozilla", ".thunderbird"
#define TARGET_LINUX 1
#else
#define CONFDIRS "Mozilla", "Thunderbird"
#define TARGET_LINUX 0
#endif

/**
 * @brief Length of string buffers used
 *
 * The maximal length of input is defined as 9999 (+ terminating \0).
 * We use it for other other input puffers besides the IPC input, too.
 * (One size fits all).
 */
#define LINEBUFLEN 10000

/**
 * @brief Global Return Code
 *
 * This will be retuned by the programm and might be set to an
 * error code on fatal errors and to and warning code on non-fatal
 * errors.  In case of mor than one warning the warning codes will be
 * ORed together.
 */
int return_code = 0;

/**
 * @brief Return configuration base directory.
 * @returns A pointer to a string containing the path to the base
 * directory holding the configuration directories for e.g. mozilla
 * and thunderbird.
 */
static char *
get_conf_basedir()
{
  char *cdir, *envvar;

  if (TARGET_LINUX)
    envvar = "HOME" ;
  else
    envvar = "APPDATA";

  if ((cdir = getenv(envvar)) != NULL)
    return cdir;
  else
    {
      DEBUGPRINTF("FATAL!  No %s in environment.\n", envvar);
      exit(ERR_MOZ_HOMELESS);
    }
}

/**
 * @brief Get a list of all mozilla profile directories
 *
 * Parse the profiles.ini and extract all profile paths from that.
 * The expected data is in the form:
 *
 * [Profile99]
 * IsRelative=1
 * Path=Example/fooo.bar
 *
 * or
 * [Profile0]
 * IsRelative=0
 * Path=c:\foo\bar\baz
 *
 * Mozilla also accepts the ini file on Windows even if it is UTF-16
 * encoded but never writes UTF-16 on its own.  So currently we ignore
 * this special case.
 *
 * @param[in] inifile_name path of the profile.ini to read.
 * @return NULL terminated array of strings containing containing the
 * absolute path of the profile directories. The array needs to
 * be freed by the caller.
 */
static char **
get_profile_dirs (char *inifile_name)
{
  char **dirs = NULL;
  char *inifile_dirname;
  FILE *inifile;
  char line[LINEBUFLEN];
  char *key;
  char *value;
  char path[LINEBUFLEN];
  char *fqpath;
  bool inprofile = false;
  bool relative_path = false;

  if ((inifile = fopen(inifile_name, "r")) != NULL)
    {
      DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name);

      inifile_dirname = port_dirname(inifile_name);
      while (fgets(line, LINEBUFLEN, inifile) != NULL)
        {
          /* Determine if we are in an profile section */
          if (str_starts_with(line, "[Profile"))
            {
              relative_path = false;
              inprofile = true;
            }
          else if (line[0] == '[')
            inprofile = false;

          /* If we are in a profile parse path related stuff */
          if (inprofile)
            {
              key = strtok(line, "=");
              value = strtok(NULL, "=");
              str_trim(&value);
              if (str_equal(key, "Path"))
                {
                  if (relative_path)
                    snprintf(path, LINEBUFLEN, "%s/%s", inifile_dirname, value);
                  else
                    strncpy(path, value, LINEBUFLEN);
                  if ((fqpath = port_realpath(path)) != NULL)
                    {
                      DEBUGPRINTF("Found profile path: '%s'\n", fqpath);
                      strv_append(&dirs, fqpath, strlen(fqpath));
                      free (fqpath);
                    }
                  else
                    {
                      DEBUGPRINTF("WARN!  Non existent profile path: '%s'\n", path);
                      return_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST;
                    }
                }
              else if (str_equal(key, "IsRelative") &&
                       str_starts_with(value, "1"))
                relative_path = true;
            }
        }
      fclose(inifile);
    }
  else
    {
      DEBUGPRINTF("WARN!  Could not open ini file: '%s'\n", inifile_name);
      return_code |= WARN_MOZ_FAILED_TO_OPEN_INI;
    }
  return dirs;
}

/**
 * @brief Search for mozilla profiles.ini files
 *
 * Use well known paths and heuristics to find the current users
 * profiles.ini files on GNU/Linux and Windows systems.
 *
 * @return NULL terminated array of strings containing the absolute
 * path of the profiles.ini files.  The array needs to be freed by the
 * caller.
 */
static char **
get_profile_inis ()
{
  char **inis = NULL;
  char path[LINEBUFLEN];
  char *fqpath;
  DIR *mozdir;
  struct dirent *mozdirent;
  char *confbase = get_conf_basedir();
  const char *confdirs[] = { CONFDIRS, NULL };

  for (int i=0; confdirs[i] != NULL; i++)
    {
      snprintf(path, LINEBUFLEN, "%s/%s",
               confbase,
               confdirs[i]);
      if ((mozdir = opendir(path)) != NULL)
        {
          while ((mozdirent = readdir(mozdir)) != NULL)
            {
              snprintf(path, LINEBUFLEN, "%s/%s/%s",
                       confbase,
                       confdirs[i],
                       mozdirent->d_name);
              if (port_isdir(path)
                  && (strcmp(mozdirent->d_name, "..") != 0))
                {
                  snprintf(path, LINEBUFLEN, "%s/%s/%s/%s",
                           confbase,
                           confdirs[i],
                           mozdirent->d_name,
                           "profiles.ini");
                  DEBUGPRINTF("checking for %s...\n", path);
                  if ((fqpath = port_realpath(path)) != NULL)
                    {
                      strv_append(&inis, fqpath, strlen(fqpath));
                      DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath);
                      free(fqpath);
                    }
                }
            }
          closedir(mozdir);
        }
      else
        {
          DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]);
        }
    }
  if (inis == NULL)
    {
      DEBUGPRINTF("No ini files found - will do nothing!\n");
      exit(WARN_MOZ_NO_PROFILES);
    }
  return inis;
}

/**
 * @brief Collect all mozilla profile directories of current user.
 * @return NULL terminated array of strings containing the absolute
 * path of the profile directories.  The array needs to be freed by the
 * caller.
 */
static char**
get_all_profile_dirs()
{
  char **mozinis, **pdirs;
  char **alldirs = NULL;
  if ((mozinis = get_profile_inis()) != NULL)
    {
      for (int i=0; mozinis[i] != NULL; i++)
        {
          pdirs =
            get_profile_dirs(mozinis[i]);
          if (pdirs != NULL)
            {
              for (int i=0; pdirs[i] != NULL; i++)
                {
                  strv_append(&alldirs, pdirs[i], strlen(pdirs[i]));
                }
              strv_free(pdirs);
            }
        }
      strv_free(mozinis);
    }
  return alldirs;
}

/**
 * @brief list certificates from nss certificate store
 * @param[in] confdir the directory with the certificate store
 */
static void
nss_list_certs (char *confdir)
{
  CERTCertList *list;
  CERTCertListNode *node;
  char *name;

  if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY)
      == SECSuccess)
    {
      list = PK11_ListCerts(PK11CertListAll, NULL);
      for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
           node = CERT_LIST_NEXT(node)) {
        name = node->appData;

        printf ("Found certificate \"%s\"\n", name);
      }
      CERT_DestroyCertList(list);
      NSS_Shutdown();
    }
  else
    DEBUGPRINTF("Could not open nss cer store in %s!", confdir);
}

static bool
base64_to_secitem(char *b64, size_t b64len, SECItem *secitem)
{
  unsigned char *dercert = NULL;
  size_t dercertlen;

  if ((str_base64_decode((char **)(&dercert), &dercertlen,
                         b64, b64len) == 0) &&
      (dercertlen > 0))
    {
      secitem->data = dercert;
      secitem->len = (unsigned int) dercertlen;
      return true;
    }
  else
    DEBUGPRINTF("Base64 decode failed for: %s\n", b64);
    return false;
}

/**
 * @brief Parse IPC commands from standard input.
 *
 * Reads command lines (R: and I:) from standard input and puts the
 * certificates to process in two SECItem lists holding the
 * certificates in DER format.
 * @param[inout] install_list list of SECItems with certifiactes to install
 * @param[inout] remove_list list of SECItems with certifiactes to remove
 */
static void
parse_commands (seciteml_t **install_list, seciteml_t **remove_list)
{
  char inpl[LINEBUFLEN];
  size_t inpllen;
  bool parserr = true;
  SECItem secitem;

  while ( fgets(inpl, LINEBUFLEN, stdin) != NULL )
    {
      inpllen = strnlen(inpl, LINEBUFLEN);
      /* Validate input line:
       * - must be (much) longer than 3 characters
       * - must start with "*:"
       */
      if ((inpllen > 3) && (inpl[1] == ':'))
        /* Now parse Input */
        switch(inpl[0])
          {
          case 'R':
            parserr = true;
            DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]);
            if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem))
              {
                seciteml_push(remove_list, &secitem);
                parserr = false;
              }
            break;
          case 'I':
            parserr = true;
            DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]);
            if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem))
              {
                seciteml_push(install_list, &secitem);
                parserr = false;
              }
            break;
          default:
            parserr = true;
          }
      else
        {
          parserr = true;
        }

      if (parserr)
        {
          DEBUGPRINTF("FATAL: Invalid input: %s\n", inpl);
          exit(ERR_MOZ_INVALID_INPUT);
        }
    }
}


int
main ()
{
  char **pdirs;
  seciteml_t *certs_to_remove = NULL;
  seciteml_t *certs_to_add = NULL;
  SECItem *secitemp;

  pdirs =
    get_all_profile_dirs();

  if (pdirs != NULL)
    {
      parse_commands(&certs_to_add, &certs_to_remove);

      while ((secitemp = seciteml_pop(&certs_to_remove)) != NULL)
        {
          fprintf(stderr,"CERT TO REMOVE :'");
          write(2, secitemp->data, secitemp->len);
          fprintf(stderr,"'\n");
          free(secitemp->data);
          free(secitemp);
        }
      while ((secitemp = seciteml_pop(&certs_to_add)) != NULL)
        {
          fprintf(stderr,"CERT TO ADD :'");
          write(2, secitemp->data, secitemp->len);
          fprintf(stderr,"'\n");
          free(secitemp->data);
          free(secitemp);
        }

      for (int i=0; pdirs[i] != NULL; i++)
        {
          puts(pdirs[i]);
          nss_list_certs(pdirs[i]);
        }
      strv_free(pdirs);
    }
  exit(return_code);
}

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