wilde@121: /** wilde@121: * @file wilde@121: * @brief Mozilla installation process aheinecke@99: * aheinecke@99: * Reads from stdin a list of instructions in the form: aheinecke@99: * aheinecke@238: * I: aheinecke@238: * R: aheinecke@99: * ... aheinecke@99: * aheinecke@238: * With one instruction per line. the maximum size of an input aheinecke@238: * line is 9999 characters (including the \r\n) at the end of the line. aheinecke@99: * aheinecke@99: * Certificates marked with I: will be installed and the ones aheinecke@99: * marked with R: will be searched and if available removed from aheinecke@99: * the databases. aheinecke@99: * aheinecke@99: * This tool tries to find all NSS databases the user has aheinecke@99: * access to and to execute the instructions on all of them. aheinecke@99: * aheinecke@99: * If there are other processes accessing the databases the caller aheinecke@99: * has to ensure that those are terminated before this process is aheinecke@99: * executed. aheinecke@99: * aheinecke@238: * If the same certificate is marked to be installed and to be removed aheinecke@238: * in one call the behavior is undefined. This should be avoided and aheinecke@238: * may lead to errors. aheinecke@238: * aheinecke@99: * Returns 0 on success (Even when no stores where found) an error value aheinecke@99: * as defined in errorcodes.h otherwise. aheinecke@99: * aheinecke@99: * Success messages are written to stdout. Errors to stderr. For logging aheinecke@99: * purposes each installation / removal of a certificate will be reported aheinecke@99: * with the profile name that it modified. aheinecke@99: * aheinecke@99: */ aheinecke@99: wilde@235: /** wilde@235: * @brief Needs to eb defined to get strnlen() wilde@235: */ wilde@235: #define _POSIX_C_SOURCE 200809L wilde@235: wilde@235: /* REMOVEME: */ wilde@235: #include wilde@235: wilde@269: #include wilde@269: #include wilde@269: #include wilde@173: #include wilde@224: #include wilde@224: #include wilde@119: #include wilde@119: #include wilde@119: #include wilde@119: #include wilde@173: #include wilde@119: wilde@230: #define DEBUGPREFIX "MOZ-" aheinecke@252: #include "logging.h" wilde@230: wilde@261: #include "certhelp.h" wilde@226: #include "errorcodes.h" wilde@226: #include "portpath.h" wilde@226: #include "strhelp.h" wilde@244: #include "nss-secitemlist.h" wilde@228: wilde@113: #ifndef _WIN32 wilde@197: #define CONFDIRS ".mozilla", ".thunderbird" wilde@223: #define TARGET_LINUX 1 wilde@113: #else wilde@197: #define CONFDIRS "Mozilla", "Thunderbird" wilde@223: #define TARGET_LINUX 0 wilde@113: #endif wilde@113: wilde@229: /** wilde@229: * @brief Length of string buffers used wilde@229: * wilde@229: * The maximal length of input is defined as 9999 (+ terminating \0). wilde@229: * We use it for other other input puffers besides the IPC input, too. wilde@229: * (One size fits all). wilde@229: */ wilde@229: #define LINEBUFLEN 10000 wilde@147: wilde@119: /** wilde@119: * @brief Global Return Code wilde@119: * wilde@119: * This will be retuned by the programm and might be set to an wilde@119: * error code on fatal errors and to and warning code on non-fatal wilde@119: * errors. In case of mor than one warning the warning codes will be wilde@119: * ORed together. wilde@119: */ wilde@119: int return_code = 0; aheinecke@44: aheinecke@44: /** wilde@194: * @brief Return configuration base directory. wilde@194: * @returns A pointer to a string containing the path to the base wilde@194: * directory holding the configuration directories for e.g. mozilla wilde@194: * and thunderbird. wilde@180: */ wilde@180: static char * wilde@194: get_conf_basedir() wilde@180: { wilde@194: char *cdir, *envvar; wilde@180: wilde@223: if (TARGET_LINUX) wilde@194: envvar = "HOME" ; wilde@180: else wilde@194: envvar = "APPDATA"; wilde@180: wilde@194: if ((cdir = getenv(envvar)) != NULL) wilde@194: return cdir; wilde@194: else wilde@180: { wilde@228: DEBUGPRINTF("FATAL! No %s in environment.\n", envvar); wilde@180: exit(ERR_MOZ_HOMELESS); wilde@180: } wilde@180: } wilde@180: wilde@180: /** wilde@119: * @brief Get a list of all mozilla profile directories aheinecke@44: * wilde@232: * Parse the profiles.ini and extract all profile paths from that. wilde@232: * The expected data is in the form: wilde@232: * wilde@232: * [Profile99] wilde@232: * IsRelative=1 wilde@232: * Path=Example/fooo.bar wilde@232: * wilde@232: * or wilde@232: * [Profile0] wilde@232: * IsRelative=0 wilde@232: * Path=c:\foo\bar\baz wilde@232: * wilde@232: * Mozilla also accepts the ini file on Windows even if it is UTF-16 wilde@232: * encoded but never writes UTF-16 on its own. So currently we ignore wilde@232: * this special case. aheinecke@44: * wilde@121: * @param[in] inifile_name path of the profile.ini to read. aheinecke@44: * @return NULL terminated array of strings containing containing the aheinecke@44: * absolute path of the profile directories. The array needs to aheinecke@44: * be freed by the caller. aheinecke@44: */ wilde@119: static char ** wilde@119: get_profile_dirs (char *inifile_name) wilde@119: { wilde@119: char **dirs = NULL; wilde@147: char *inifile_dirname; wilde@119: FILE *inifile; wilde@147: char line[LINEBUFLEN]; wilde@147: char *key; wilde@147: char *value; wilde@147: char path[LINEBUFLEN]; wilde@147: char *fqpath; wilde@119: bool inprofile = false; wilde@147: bool relative_path = false; aheinecke@44: wilde@119: if ((inifile = fopen(inifile_name, "r")) != NULL) wilde@119: { wilde@228: DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name); wilde@175: wilde@147: inifile_dirname = port_dirname(inifile_name); wilde@147: while (fgets(line, LINEBUFLEN, inifile) != NULL) wilde@119: { wilde@147: /* Determine if we are in an profile section */ wilde@147: if (str_starts_with(line, "[Profile")) wilde@147: { wilde@147: relative_path = false; wilde@147: inprofile = true; wilde@147: } wilde@119: else if (line[0] == '[') wilde@119: inprofile = false; wilde@147: wilde@147: /* If we are in a profile parse path related stuff */ wilde@147: if (inprofile) wilde@147: { wilde@157: key = strtok(line, "="); wilde@157: value = strtok(NULL, "="); wilde@147: str_trim(&value); wilde@147: if (str_equal(key, "Path")) wilde@147: { wilde@147: if (relative_path) wilde@147: snprintf(path, LINEBUFLEN, "%s/%s", inifile_dirname, value); wilde@147: else wilde@147: strncpy(path, value, LINEBUFLEN); wilde@147: if ((fqpath = port_realpath(path)) != NULL) wilde@147: { wilde@228: DEBUGPRINTF("Found profile path: '%s'\n", fqpath); wilde@147: strv_append(&dirs, fqpath, strlen(fqpath)); wilde@147: free (fqpath); wilde@147: } wilde@147: else wilde@175: { wilde@228: DEBUGPRINTF("WARN! Non existent profile path: '%s'\n", path); wilde@175: return_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; wilde@175: } wilde@147: } wilde@147: else if (str_equal(key, "IsRelative") && wilde@147: str_starts_with(value, "1")) wilde@147: relative_path = true; wilde@147: } wilde@119: } bernhard@179: fclose(inifile); wilde@119: } wilde@119: else wilde@119: { wilde@228: DEBUGPRINTF("WARN! Could not open ini file: '%s'\n", inifile_name); wilde@119: return_code |= WARN_MOZ_FAILED_TO_OPEN_INI; wilde@119: } wilde@119: return dirs; wilde@119: } aheinecke@44: wilde@173: /** wilde@173: * @brief Search for mozilla profiles.ini files wilde@173: * wilde@173: * Use well known paths and heuristics to find the current users wilde@173: * profiles.ini files on GNU/Linux and Windows systems. wilde@173: * wilde@173: * @return NULL terminated array of strings containing the absolute wilde@173: * path of the profiles.ini files. The array needs to be freed by the wilde@173: * caller. wilde@173: */ wilde@173: static char ** wilde@173: get_profile_inis () wilde@173: { wilde@173: char **inis = NULL; wilde@173: char path[LINEBUFLEN]; wilde@173: char *fqpath; wilde@173: DIR *mozdir; wilde@173: struct dirent *mozdirent; wilde@194: char *confbase = get_conf_basedir(); wilde@197: const char *confdirs[] = { CONFDIRS, NULL }; wilde@173: wilde@197: for (int i=0; confdirs[i] != NULL; i++) wilde@173: { wilde@197: snprintf(path, LINEBUFLEN, "%s/%s", wilde@197: confbase, wilde@197: confdirs[i]); wilde@197: if ((mozdir = opendir(path)) != NULL) wilde@180: { wilde@197: while ((mozdirent = readdir(mozdir)) != NULL) wilde@180: { wilde@197: snprintf(path, LINEBUFLEN, "%s/%s/%s", wilde@194: confbase, wilde@197: confdirs[i], wilde@197: mozdirent->d_name); wilde@197: if (port_isdir(path) wilde@197: && (strcmp(mozdirent->d_name, "..") != 0)) wilde@180: { wilde@197: snprintf(path, LINEBUFLEN, "%s/%s/%s/%s", wilde@197: confbase, wilde@197: confdirs[i], wilde@197: mozdirent->d_name, wilde@197: "profiles.ini"); wilde@228: DEBUGPRINTF("checking for %s...\n", path); wilde@197: if ((fqpath = port_realpath(path)) != NULL) wilde@197: { wilde@197: strv_append(&inis, fqpath, strlen(fqpath)); wilde@228: DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); wilde@197: free(fqpath); wilde@197: } wilde@180: } wilde@180: } wilde@197: closedir(mozdir); wilde@180: } wilde@197: else wilde@197: { wilde@228: DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); wilde@197: } wilde@180: } wilde@197: if (inis == NULL) wilde@180: { wilde@228: DEBUGPRINTF("No ini files found - will do nothing!\n"); wilde@173: } wilde@173: return inis; wilde@173: } wilde@173: wilde@223: /** wilde@231: * @brief Collect all mozilla profile directories of current user. wilde@231: * @return NULL terminated array of strings containing the absolute wilde@231: * path of the profile directories. The array needs to be freed by the wilde@231: * caller. wilde@231: */ wilde@231: static char** wilde@231: get_all_profile_dirs() wilde@231: { wilde@231: char **mozinis, **pdirs; wilde@231: char **alldirs = NULL; wilde@231: if ((mozinis = get_profile_inis()) != NULL) wilde@231: { wilde@231: for (int i=0; mozinis[i] != NULL; i++) wilde@231: { wilde@231: pdirs = wilde@231: get_profile_dirs(mozinis[i]); wilde@231: if (pdirs != NULL) wilde@231: { wilde@231: for (int i=0; pdirs[i] != NULL; i++) wilde@231: { wilde@231: strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); wilde@231: } wilde@231: strv_free(pdirs); wilde@231: } wilde@231: } wilde@231: strv_free(mozinis); wilde@231: } wilde@231: return alldirs; wilde@231: } wilde@231: wilde@231: /** wilde@223: * @brief list certificates from nss certificate store wilde@223: * @param[in] confdir the directory with the certificate store wilde@223: */ wilde@223: static void wilde@223: nss_list_certs (char *confdir) wilde@223: { wilde@223: CERTCertList *list; wilde@223: CERTCertListNode *node; wilde@223: char *name; wilde@224: wilde@223: if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) wilde@223: == SECSuccess) wilde@223: { wilde@223: list = PK11_ListCerts(PK11CertListAll, NULL); wilde@223: for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); wilde@223: node = CERT_LIST_NEXT(node)) { wilde@223: name = node->appData; wilde@223: wilde@223: printf ("Found certificate \"%s\"\n", name); wilde@223: } wilde@223: CERT_DestroyCertList(list); wilde@223: NSS_Shutdown(); wilde@223: } wilde@223: else wilde@263: DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); wilde@223: } wilde@223: wilde@261: /** wilde@261: * @brief Create a string with the name for cert in SECItem. wilde@261: * wilde@261: * Should be freed by caller. wilde@261: * @param[in] secitemp ponts to an SECItem holding the DER certificate. wilde@261: * @retruns a string of the from "CN of Subject - O of Subject" wilde@261: */ wilde@261: static char * wilde@261: nss_cert_name(SECItem *secitemp) wilde@261: { char *cn_str, *o_str, *name; wilde@261: size_t name_len; wilde@261: cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); wilde@261: o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); wilde@261: name_len = strlen(cn_str) + strlen(o_str) + 4; wilde@261: name = (char *)xmalloc(name_len); wilde@261: snprintf(name, name_len, "%s - %s", cn_str, o_str); wilde@261: free(cn_str); wilde@261: free(o_str); wilde@261: return name; wilde@261: } wilde@261: wilde@276: /** wilde@276: * @brief Convert a base64 encoded DER certificate to SECItem wilde@276: * @param[in] b64 pointer to the base64 encoded certificate wilde@276: * @param[in] b64len length of the base64 encoded certificate wilde@276: * @param[out] secitem pointer to the SECItem in which to store the wilde@276: * raw DER certifiacte. wilde@276: * @returns true on success and false on failure wilde@276: */ wilde@244: static bool wilde@244: base64_to_secitem(char *b64, size_t b64len, SECItem *secitem) wilde@244: { wilde@244: unsigned char *dercert = NULL; wilde@244: size_t dercertlen; wilde@244: wilde@245: if ((str_base64_decode((char **)(&dercert), &dercertlen, wilde@245: b64, b64len) == 0) && wilde@245: (dercertlen > 0)) wilde@244: { wilde@244: secitem->data = dercert; wilde@246: secitem->len = (unsigned int) dercertlen; wilde@244: return true; wilde@244: } wilde@244: else wilde@244: DEBUGPRINTF("Base64 decode failed for: %s\n", b64); wilde@261: return false; wilde@244: } wilde@244: wilde@244: /** wilde@277: * @brief Store DER certificate in mozilla store. wilde@277: * @param[in] pdir the mozilla profile directory with the certificate wilde@277: * store to manipulate. wilde@277: * @param[in] dercert pointer to a SECItem holding the DER certificate wilde@277: * to install wilde@277: * @returns true on success and false on failure wilde@277: */ wilde@277: static bool wilde@277: import_cert(char *pdir, SECItem *dercert) wilde@277: { wilde@277: PK11SlotInfo *pk11slot = NULL; wilde@277: bool success = false; wilde@277: char *cert_name = nss_cert_name(dercert); wilde@277: wilde@277: DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); wilde@277: if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) wilde@277: { wilde@277: pk11slot = PK11_GetInternalKeySlot(); wilde@277: if (PK11_ImportDERCert(pk11slot, dercert, CK_INVALID_HANDLE, wilde@277: cert_name, PR_FALSE) wilde@277: == SECSuccess) wilde@277: { wilde@277: success = true; wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); wilde@277: } wilde@277: PK11_FreeSlot(pk11slot); wilde@277: NSS_Shutdown(); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); wilde@277: } wilde@277: wilde@277: free(cert_name); wilde@277: return success; wilde@277: } wilde@277: wilde@277: /** wilde@277: * @brief Remove DER certificate from mozilla store. wilde@277: * @param[in] pdir the mozilla profile directory with the certificate wilde@277: * store to manipulate. wilde@277: * @param[in] dercert pointer to a SECItem holding the DER certificate wilde@277: * to remove wilde@277: * @returns true on success and false on failure wilde@277: */ wilde@277: static bool wilde@277: remove_cert(char *pdir, SECItem *dercert) wilde@277: { wilde@277: PK11SlotInfo *pk11slot = NULL; wilde@277: bool success = false; wilde@277: char *cert_name = nss_cert_name(dercert); wilde@277: CERTCertificate *cert = NULL; wilde@277: wilde@277: DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); wilde@277: if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) wilde@277: { wilde@277: pk11slot = PK11_GetInternalKeySlot(); wilde@277: cert = PK11_FindCertFromDERCertItem(pk11slot, wilde@277: dercert, NULL); wilde@277: if (cert != NULL) wilde@277: { wilde@277: if (SEC_DeletePermCertificate(cert) == SECSuccess) wilde@277: { wilde@277: success = true; wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); wilde@277: } wilde@277: CERT_DestroyCertificate(cert); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); wilde@277: } wilde@277: PK11_FreeSlot(pk11slot); wilde@277: NSS_Shutdown(); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); wilde@277: } wilde@277: free(cert_name); wilde@277: return success; wilde@277: } wilde@277: wilde@277: /** wilde@279: * @brief Apply a function to a list of certificates and profiles wilde@279: * wilde@279: * The function must have the signature: wilde@279: * wilde@279: * bool function(char *pdir, SECItem der_cert) wilde@279: * wilde@279: * where pdir is the path of an profile and der_cert is an raw DER wilde@279: * formatted certificate. The function must return true on success wilde@279: * and false on failure. wilde@279: * wilde@279: * This function is intended wor use with the import_cert and wilde@279: * remove_cert functions. wilde@279: * wilde@279: * @param[in] fn the function to apply wilde@279: * @param[inout] certs a secitem list holding the certificates wilde@279: * the list will be change (emptied)! wilde@279: * @param[in] pdirs the NULL terminated list of profile directories wilde@279: * @returns true on success and false on failure wilde@279: */ wilde@279: bool wilde@279: apply_to_certs_and_profiles(bool fn(char *, SECItem *), wilde@279: seciteml_t **certs, char **pdirs) wilde@279: { wilde@279: SECItem *cert; wilde@279: bool success = true; wilde@279: wilde@279: while ((cert = seciteml_pop(certs)) != NULL) wilde@279: { wilde@279: for (int i=0; pdirs[i] != NULL; i++) wilde@279: { wilde@279: if (! (*fn)(pdirs[i], cert)) wilde@279: success = false; wilde@279: } wilde@279: free(cert->data); wilde@279: free(cert); wilde@279: } wilde@279: wilde@279: return success; wilde@279: } wilde@279: wilde@279: /** wilde@244: * @brief Parse IPC commands from standard input. wilde@244: * wilde@244: * Reads command lines (R: and I:) from standard input and puts the wilde@244: * certificates to process in two SECItem lists holding the wilde@244: * certificates in DER format. wilde@244: * @param[inout] install_list list of SECItems with certifiactes to install wilde@244: * @param[inout] remove_list list of SECItems with certifiactes to remove wilde@244: */ wilde@244: static void wilde@244: parse_commands (seciteml_t **install_list, seciteml_t **remove_list) wilde@244: { wilde@244: char inpl[LINEBUFLEN]; wilde@244: size_t inpllen; wilde@244: bool parserr = true; wilde@244: SECItem secitem; wilde@244: wilde@244: while ( fgets(inpl, LINEBUFLEN, stdin) != NULL ) wilde@244: { wilde@244: inpllen = strnlen(inpl, LINEBUFLEN); wilde@244: /* Validate input line: wilde@244: * - must be (much) longer than 3 characters wilde@244: * - must start with "*:" wilde@244: */ wilde@244: if ((inpllen > 3) && (inpl[1] == ':')) wilde@244: /* Now parse Input */ wilde@244: switch(inpl[0]) wilde@244: { wilde@244: case 'R': wilde@244: parserr = true; wilde@244: DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]); wilde@244: if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) wilde@244: { wilde@244: seciteml_push(remove_list, &secitem); wilde@244: parserr = false; wilde@244: } wilde@244: break; wilde@244: case 'I': wilde@244: parserr = true; wilde@244: DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]); wilde@244: if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) wilde@244: { wilde@244: seciteml_push(install_list, &secitem); wilde@244: parserr = false; wilde@244: } wilde@244: break; wilde@244: default: wilde@244: parserr = true; wilde@244: } wilde@244: else wilde@244: { wilde@244: parserr = true; wilde@244: } wilde@244: wilde@244: if (parserr) wilde@244: { wilde@244: DEBUGPRINTF("FATAL: Invalid input: %s\n", inpl); wilde@244: exit(ERR_MOZ_INVALID_INPUT); wilde@244: } wilde@244: } wilde@244: } wilde@244: wilde@173: wilde@113: int wilde@173: main () wilde@113: { wilde@231: char **pdirs; wilde@244: seciteml_t *certs_to_remove = NULL; wilde@244: seciteml_t *certs_to_add = NULL; wilde@244: wilde@231: pdirs = wilde@231: get_all_profile_dirs(); wilde@235: wilde@231: if (pdirs != NULL) wilde@231: { wilde@244: parse_commands(&certs_to_add, &certs_to_remove); wilde@244: wilde@279: puts("OLD List of installed certs:"); wilde@279: for (int i=0; pdirs[i] != NULL; i++) wilde@279: nss_list_certs(pdirs[i]); wilde@263: wilde@279: if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, pdirs)) wilde@279: return_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; wilde@279: wilde@279: if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, pdirs)) wilde@279: return_code |= WARN_MOZ_COULD_NOT_ADD_CERT; wilde@279: wilde@279: puts("NEW List of installed certs:"); wilde@279: for (int i=0; pdirs[i] != NULL; i++) wilde@279: nss_list_certs(pdirs[i]); wilde@279: wilde@231: strv_free(pdirs); wilde@231: } wilde@119: exit(return_code); aheinecke@44: }