Mercurial > trustbridge
view cinst/mozilla.c @ 982:85c497b45488
Merged.
author | Emanuel Schuetze <emanuel@intevation.de> |
---|---|
date | Fri, 29 Aug 2014 16:08:50 +0200 |
parents | b3695a3399de |
children | 1743895b39b8 |
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. */ /** * @file * @brief Mozilla installation process * * Reads from a file given on command line or 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 the tool is executed with a UID of 0 or with admin privileges under * windows it will not look into the user directories but instead try * to write the system wide defaults. * * 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 be defined to get strnlen() */ #define _POSIX_C_SOURCE 200809L /* REMOVEME: */ #include <unistd.h> #include <cert.h> #include <certdb.h> #include <certt.h> #include <dirent.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 "logging.h" #include "certhelp.h" #include "errorcodes.h" #include "portpath.h" #include "strhelp.h" #include "nss-secitemlist.h" #include "util.h" #ifndef _WIN32 #define CONFDIRS ".mozilla", ".thunderbird" /* Default installation directory of ubuntu 14.4 is respected */ #define MOZILLA_DEFAULTS "/etc/thunderbird", "/etc/firefox" #define NSSSHARED ".pki/nssdb" #define NSSSHARED_GLOBAL "/etc/pki/nssdb" #define TARGET_LINUX 1 #else #define MOZILLA_DEFAULTS 0 #define CONFDIRS "Mozilla", "Thunderbird" #define NSSSHARED "" #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 #ifdef _WIN32 #define STRTOK_R strtok_s #else #define STRTOK_R strtok_r #endif /** * @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 exit_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 = NULL; char *fqpath; bool inprofile = false; bool relative_path = false; char *saveptr; 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) { saveptr = NULL; key = STRTOK_R(line, "=", &saveptr); value = STRTOK_R(NULL, "=", &saveptr); str_trim(&value); if (str_equal(key, "Path")) { if (relative_path) xasprintf(&path, "%s/%s", inifile_dirname, value); else xasprintf(&path, "%s", value); 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); exit_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; } free(path); } 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); exit_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 *mozpath, *fqpath, *subpath, *ppath; DIR *mozdir; struct dirent *mozdirent; char *confbase = get_conf_basedir(); const char *confdirs[] = { CONFDIRS, NULL }; for (int i=0; confdirs[i] != NULL; i++) { xasprintf(&mozpath,"%s/%s", confbase, confdirs[i]); if ((mozdir = opendir(mozpath)) != NULL) { while ((mozdirent = readdir(mozdir)) != NULL) { xasprintf(&subpath, "%s/%s/%s", confbase, confdirs[i], mozdirent->d_name); if (port_isdir(subpath) && (strcmp(mozdirent->d_name, "..") != 0)) { xasprintf(&ppath, "%s/%s/%s/%s", confbase, confdirs[i], mozdirent->d_name, "profiles.ini"); DEBUGPRINTF("checking for %s...\n", ppath); if ((fqpath = port_realpath(ppath)) != NULL) { strv_append(&inis, fqpath, strlen(fqpath)); DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); free(fqpath); } free(ppath); } free(subpath); } closedir(mozdir); } else { DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); } free(mozpath); } if (inis == NULL) { DEBUGPRINTF("No ini files found - will do nothing!\n"); } return inis; } /** * @brief Collect the default profile directories for mozilla software * * If the default directory is found but not the profiles subdirectory * this will create the profiles subdirectory. * * @return NULL terminated array of strings containing the absolute path * to the default profile directories. Needs to be freed by the caller. */ static char** get_default_profile_dirs() { char **retval = NULL; const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; for (int i=0; confdirs[i] != NULL; i++) { char * realpath = port_realpath(confdirs[i]); char * profile_dir = NULL; if (realpath == NULL) { DEBUGPRINTF ("Did not find directory: '%s'\n", confdirs[i]); continue; } xasprintf(&profile_dir, "%s/profile", realpath); if (port_isdir(profile_dir)) { DEBUGPRINTF("Found default directory: '%s'\n", profile_dir); /* All is well */ strv_append (&retval, profile_dir, strlen(profile_dir)); xfree(profile_dir); profile_dir = NULL; continue; } else { /* Create the directory */ if (port_fileexits(profile_dir)) { DEBUGPRINTF ("Path: '%s' is not a directory but it exists. Skipping.\n", profile_dir); xfree(profile_dir); profile_dir = NULL; continue; } else { /* Lets create it */ if (!port_mkdir(profile_dir)) { ERRORPRINTF ("Failed to create directory: '%s'\n", profile_dir); xfree(profile_dir); profile_dir = NULL; continue; } strv_append (&retval, profile_dir, strlen(profile_dir)); xfree(profile_dir); profile_dir = NULL; } } } return retval; } /** * @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_nssdb_dirs() { char **mozinis, **pdirs; char **alldirs = NULL; if (is_elevated()) { #ifndef _WIN32 /* NSS Shared db does not exist under windows. */ strv_append(&alldirs, NSSSHARED_GLOBAL, strlen(NSSSHARED_GLOBAL)); #endif pdirs = get_default_profile_dirs(); if (pdirs != NULL) { for (int i=0; pdirs[i] != NULL; i++) { strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); } strv_free(pdirs); } return alldirs; } /* Search Mozilla/Firefox/Thunderbird profiles */ 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); } /* Search for NSS shared DB (used by Chrome/Chromium on GNU/Linux) */ if (TARGET_LINUX) { char *path, *fqpath, *sqlpath; xasprintf(&path, "%s/%s", get_conf_basedir(), NSSSHARED); if ((fqpath = port_realpath(path)) != NULL) { xasprintf(&sqlpath, "sql:%s", fqpath); strv_append(&alldirs, sqlpath, strlen(sqlpath)); free(sqlpath); free(fqpath); } free(path); } return alldirs; } #ifdef DEBUGOUTPUT /** * @brief list certificates from nss certificate store * @param[in] confdir the directory with the certificate store */ static void DEBUG_nss_list_certs (char *confdir) { CERTCertList *list; CERTCertListNode *node; char *name; if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) == SECSuccess) { DEBUGPRINTF("Listing certs in \"%s\"\n", confdir); list = PK11_ListCerts(PK11CertListAll, NULL); for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); node = CERT_LIST_NEXT(node)) { name = node->appData; DEBUGPRINTF("Found certificate \"%s\"\n", name); } /* According to valgrind this leaks memory in the list. We could not find API documentation to better free this so we accept the leakage here in case of debug. */ CERT_DestroyCertList(list); NSS_Shutdown(); } else { DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); } } #endif /** * @brief Create a string with the name for cert in SECItem. * * Should be freed by caller. * @param[in] secitemp ponts to an SECItem holding the DER certificate. * @retruns a string of the from "CN of Subject - O of Subject" */ static char * nss_cert_name(SECItem *secitemp) { char *cn_str, *o_str, *name; size_t name_len; cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); if (!cn_str || !o_str) { DEBUGPRINTF("FATAL: Could not parse certificate!"); exit(ERR_INVALID_CERT); } name_len = strlen(cn_str) + strlen(o_str) + 4; name = (char *)xmalloc(name_len); snprintf(name, name_len, "%s - %s", cn_str, o_str); free(cn_str); free(o_str); return name; } /** * @brief Convert a base64 encoded DER certificate to SECItem * @param[in] b64 pointer to the base64 encoded certificate * @param[in] b64len length of the base64 encoded certificate * @param[out] secitem pointer to the SECItem in which to store the * raw DER certifiacte. * @returns true on success and false on failure */ 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 Store DER certificate in mozilla store. * @param[in] pdir the mozilla profile directory with the certificate * store to manipulate. * @param[in] dercert pointer to a SECItem holding the DER certificate * to install * @returns true on success and false on failure */ static bool import_cert(char *pdir, SECItem *dercert) { PK11SlotInfo *pk11slot = NULL; CERTCertTrust *trust = NULL; CERTCertificate *cert = NULL; bool success = false; char *cert_name = nss_cert_name(dercert); DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); pk11slot = PK11_GetInternalKeySlot(); cert = CERT_DecodeCertFromPackage((char *)dercert->data, (int)dercert->len); trust = (CERTCertTrust *)xmalloc(sizeof(CERTCertTrust)); CERT_DecodeTrustString(trust, "C,C,C"); if ((PK11_ImportCert(pk11slot, cert, CK_INVALID_HANDLE, cert_name, PR_FALSE) == SECSuccess) && (CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess)) { log_certificate_der (pdir, dercert->data, dercert->len, true); success = true; } else { DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); ERRORPRINTF("Error installing certificate err: %i\n", PORT_GetError()); } CERT_DestroyCertificate (cert); free(trust); PK11_FreeSlot(pk11slot); free(cert_name); return success; } /** * @brief Remove DER certificate from mozilla store. * @param[in] pdir the mozilla profile directory with the certificate * store to manipulate. * @param[in] dercert pointer to a SECItem holding the DER certificate * to remove * @returns true on success and false on failure */ static bool remove_cert(char *pdir, SECItem *dercert) { PK11SlotInfo *pk11slot = NULL; bool success = false; char *cert_name = nss_cert_name(dercert); CERTCertificate *cert = NULL; DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) { pk11slot = PK11_GetInternalKeySlot(); cert = PK11_FindCertFromDERCertItem(pk11slot, dercert, NULL); if (cert != NULL) { if (SEC_DeletePermCertificate(cert) == SECSuccess) { success = true; log_certificate_der (pdir, dercert->data, dercert->len, false); } else { DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); } CERT_DestroyCertificate(cert); } else { DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); } PK11_FreeSlot(pk11slot); NSS_Shutdown(); } else { DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); } free(cert_name); return success; } /** * @brief Apply a function to a list of certificates and profiles * * The function must have the signature: * * bool function(char *pdir, SECItem der_cert) * * where pdir is the path of an profile and der_cert is an raw DER * formatted certificate. The function must return true on success * and false on failure. * * This function is intended for use with the import_cert and * remove_cert functions. * * @param[in] fn the function to apply * @param[inout] certs a secitem list holding the certificates * the list will be change (emptied)! * @param[in] pdirs the NULL terminated list of profile directories * @returns true on success and false on failure */ bool apply_to_certs_and_profiles(bool fn(char *, SECItem *), seciteml_t **certs, char **pdirs) { bool success = true; for (int i=0; pdirs[i] != NULL; i++) { seciteml_t *iter = *certs; if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) != SECSuccess) { DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdirs[i]); continue; } while (iter != NULL && iter->item != NULL) { SECItem *cert = iter->item; if (! (*fn)(pdirs[i], cert)) success = false; iter = iter->next; } NSS_Shutdown(); } seciteml_free(certs); return success; } /** * @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 (FILE *stream, seciteml_t **install_list, seciteml_t **remove_list) { char inpl[LINEBUFLEN]; size_t inpllen; bool parserr = true; SECItem secitem; while ( fgets(inpl, LINEBUFLEN, stream) != 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 (int argc, char **argv) { char **dbdirs; seciteml_t *certs_to_remove = NULL; seciteml_t *certs_to_add = NULL; FILE *input_stream; switch (argc) { case 1: DEBUGPRINTF("Opening STDIN for input...\n"); input_stream = stdin; break; case 2: DEBUGPRINTF("Opening %s for input...\n", argv[1]); if ((input_stream = fopen(argv[1], "r")) == NULL) { DEBUGPRINTF("FATAL: Could not open %s for reading!\n", argv[1]); exit_code = ERR_MOZ_FAILED_TO_OPEN_INPUT; goto exit; } break; default: DEBUGPRINTF("FATAL: Wrong number of arguments!\n"); exit_code = ERR_MOZ_WRONG_ARGC; goto exit; } dbdirs = get_all_nssdb_dirs(); if (dbdirs != NULL) { parse_commands(input_stream, &certs_to_add, &certs_to_remove); #ifdef DEBUGOUTPUT DEBUGPRINTF("OLD List of installed certs:\n"); for (int i=0; dbdirs[i] != NULL; i++) DEBUG_nss_list_certs(dbdirs[i]); #endif if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, dbdirs)) exit_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, dbdirs)) exit_code |= WARN_MOZ_COULD_NOT_ADD_CERT; #ifdef DEBUGOUTPUT DEBUGPRINTF("NEW List of installed certs:\n"); for (int i=0; dbdirs[i] != NULL; i++) DEBUG_nss_list_certs(dbdirs[i]); #endif strv_free(dbdirs); } fclose(input_stream); exit: exit(exit_code); }