Mercurial > trustbridge
view cinst/mozilla.c @ 270:9d2ac9b6a5b0
Merged
author | Sascha Wilde <wilde@intevation.de> |
---|---|
date | Wed, 02 Apr 2014 10:31:08 +0200 |
parents | f7471604bb31 |
children | ea9c5bbc6496 |
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 <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" #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 certificate store in %s!\n", confdir); } /** * @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); 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; } 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; SECStatus rv; PK11SlotInfo *pk11slot = NULL; char *cert_name; CERTCertificate *cert = NULL; pdirs = get_all_profile_dirs(); if (pdirs != NULL) { parse_commands(&certs_to_add, &certs_to_remove); while ((secitemp = seciteml_pop(&certs_to_remove)) != NULL) { cert_name = nss_cert_name(secitemp); for (int i=0; pdirs[i] != NULL; i++) { puts(pdirs[i]); nss_list_certs(pdirs[i]); printf("Will now DELETE cert: '%s' from %s\n", cert_name, pdirs[i]); if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) == SECSuccess) { pk11slot = PK11_GetInternalKeySlot(); cert = PK11_FindCertFromDERCertItem(pk11slot, secitemp, NULL); if (cert != NULL) { rv = SEC_DeletePermCertificate(cert); if (rv != SECSuccess) { DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdirs[i]); DEBUGPRINTF("Error was %d\n", rv); } } else { DEBUGPRINTF("Could not find Certificate %s in store.\n", cert_name); } CERT_DestroyCertificate(cert); PK11_FreeSlot(pk11slot); NSS_Shutdown(); } puts("List new:"); nss_list_certs(pdirs[i]); } free(cert_name); free(secitemp->data); free(secitemp); } while ((secitemp = seciteml_pop(&certs_to_add)) != NULL) { cert_name = nss_cert_name(secitemp); for (int i=0; pdirs[i] != NULL; i++) { puts(pdirs[i]); nss_list_certs(pdirs[i]); printf("Will now ADD cert: '%s' to %s\n", cert_name, pdirs[i]); if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) == SECSuccess) { pk11slot = PK11_GetInternalKeySlot(); rv = PK11_ImportDERCert(pk11slot, secitemp, CK_INVALID_HANDLE, cert_name, PR_FALSE); if (rv != SECSuccess) { DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdirs[i]); DEBUGPRINTF("Error was %d\n", rv); } PK11_FreeSlot(pk11slot); NSS_Shutdown(); } puts("List new:"); nss_list_certs(pdirs[i]); } free(cert_name); free(secitemp->data); free(secitemp); } strv_free(pdirs); } exit(return_code); }