aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ aheinecke@137: /** andre@1176: * @file certificate-installer.c andre@1176: * @brief Main entry point for the certificate install process. aheinecke@25: * andre@1176: * The certificate installer process may or may not be run with elevated aheinecke@25: * privileges. When run with elevated privileges this aheinecke@25: * process will modify system wide certificate stores. aheinecke@25: * Otherwise only the users certificate stores are modified. aheinecke@25: * emanuel@1053: * The first parameter to this process should be list=\ aheinecke@289: * of the certificate list to work on. The second parameter should emanuel@1053: * be choices=\|uninstall aheinecke@59: * andre@382: * choices_file_name should be the absolute path to an andre@382: * choices file formatted as: aheinecke@289: * emanuel@1053: * I:\
emanuel@1053: * R:\ aheinecke@59: * andre@382: * Line breaks can be system dependent in the Choices file. aheinecke@289: * andre@382: * It will only execute the choices if the andre@382: * I and R choices are also part of the signed aheinecke@59: * certificate list. The signature is validated with the aheinecke@59: * built in key. aheinecke@59: * aheinecke@289: * The special instruction "uninstall" will cause the installer aheinecke@59: * to remove all certificates (Even those marked with I) that aheinecke@289: * are part of the list. aheinecke@25: * andre@1060: * For more verbose debug output add --debug to the call. andre@1060: * aheinecke@25: **/ aheinecke@25: #include aheinecke@64: #include aheinecke@59: #include aheinecke@59: #include aheinecke@148: #include aheinecke@59: aheinecke@59: #include "strhelp.h" aheinecke@59: #include "listutil.h" aheinecke@289: #include "logging.h" aheinecke@60: #include "errorcodes.h" aheinecke@137: #include "windowsstore.h" andre@302: #include "nssstore.h" andre@1157: #include "portpath.h" aheinecke@125: andre@382: /* The certificate list + choices may only be so long as aheinecke@68: * twice the accepted certificatelist size */ aheinecke@68: #define MAX_INPUT_SIZE MAX_LINE_LENGTH * MAX_LINES * 2 aheinecke@68: aheinecke@59: /* @brief Read stdin into data structures. aheinecke@59: * andre@382: * Reads choices from an input file into the to_install aheinecke@289: * and to_remove buffers. aheinecke@289: * andre@382: * Lines starting with I: are treated as install choices. andre@382: * Lines starting with R: are treated as remove choices. aheinecke@289: * Other lines are ignored. aheinecke@289: * aheinecke@59: * Terminates in OOM conditions. aheinecke@59: * aheinecke@60: * The caller needs to free the memory allocated by this function aheinecke@60: * even when an error is returned. aheinecke@60: * andre@382: * @param[in] file_name absolute path to the choices file. andre@382: * @param[out] to_install strv of installation choices or NULL andre@382: * @param[out] to_remove strv of remove choices or NULL aheinecke@148: * aheinecke@60: * @returns: 0 on success. An error code otherwise. aheinecke@59: */ aheinecke@289: static int andre@382: read_choices_file (char *file_name, char ***to_install, andre@905: char ***to_remove) aheinecke@59: { aheinecke@163: int lines_read = 0; aheinecke@163: char buf[MAX_LINE_LENGTH + 2]; aheinecke@289: FILE *f = NULL; aheinecke@289: long file_size; aheinecke@59: aheinecke@289: if (*to_install || *to_remove) aheinecke@163: { andre@626: ERRORPRINTF ("Error invalid parameters.\n"); aheinecke@163: return -1; aheinecke@90: } aheinecke@90: andre@1157: f = port_fopen_rb(file_name, false); aheinecke@289: if (f == NULL) andre@667: { andre@667: ERRORPRINTF ("Failed to open file: %s\n", file_name); andre@667: return ERR_NO_INSTRUCTIONS; andre@667: } aheinecke@289: aheinecke@289: fseek (f, 0, SEEK_END); aheinecke@289: file_size = ftell (f); aheinecke@289: if (file_size <= 0) aheinecke@163: { aheinecke@289: fclose (f); andre@667: ERRORPRINTF ("File size error: %s\n", file_name); aheinecke@289: return ERR_NO_INSTRUCTIONS; aheinecke@289: } aheinecke@289: aheinecke@289: fseek (f, 0, SEEK_SET); aheinecke@289: aheinecke@289: if (file_size + 1 == 0) aheinecke@289: { aheinecke@289: fclose (f); andre@667: ERRORPRINTF ("File seek error: %s\n", file_name); aheinecke@289: return ERR_INVALID_INSTRUCTIONS; aheinecke@289: } aheinecke@289: aheinecke@289: while (fgets (buf, MAX_LINE_LENGTH + 1, f) ) aheinecke@289: { aheinecke@289: size_t len = strlen (buf); /* fgets ensures buf is terminated */ aheinecke@163: if (len <= 3) aheinecke@163: { andre@626: ERRORPRINTF ("Line too short.\n"); aheinecke@339: fclose (f); aheinecke@163: return ERR_INVALID_INPUT; aheinecke@91: } aheinecke@163: if (lines_read++ > MAX_LINES) aheinecke@163: { andre@626: ERRORPRINTF ("Too many lines\n"); aheinecke@339: fclose (f); aheinecke@163: return ERR_TOO_MUCH_INPUT; aheinecke@59: } aheinecke@163: if (*buf == 'I') aheinecke@163: { aheinecke@291: char *trimmed = buf+2; aheinecke@291: /* Remove leading I: and trailing whitespace */ aheinecke@291: str_trim(&trimmed); aheinecke@291: strv_append (to_install, trimmed, strlen(trimmed)); aheinecke@163: continue; aheinecke@163: } aheinecke@163: if (*buf == 'R') aheinecke@163: { aheinecke@291: char *trimmed = buf+2; aheinecke@291: /* Remove leading R: and trailing whitespace */ aheinecke@291: str_trim(&trimmed); aheinecke@291: strv_append (to_remove, trimmed, strlen(trimmed)); aheinecke@163: continue; aheinecke@59: } aheinecke@59: } aheinecke@59: aheinecke@339: fclose (f); aheinecke@163: return 0; aheinecke@59: } aheinecke@148: aheinecke@148: /** @brief Check that the insturctions match to the list aheinecke@148: * aheinecke@148: * Only certificates part of the certificate_list are allowed aheinecke@148: * for installation. aheinecke@148: * aheinecke@148: * @param[in] all_certs strv of all valid certificates in a list andre@382: * @param[in] to_validate strv of choices aheinecke@148: * aheinecke@148: * @returns 0 on success, an error otherwise aheinecke@148: */ aheinecke@163: int andre@382: validate_choices (char **all_certs, char **to_validate) aheinecke@64: { aheinecke@163: int i = 0, j = 0; aheinecke@148: aheinecke@163: if (!all_certs || strv_length (all_certs) < 1) aheinecke@163: { aheinecke@163: /* Invalid parameters */ aheinecke@163: return -1; aheinecke@148: } aheinecke@148: aheinecke@163: if (to_validate == NULL) aheinecke@163: { aheinecke@163: /* Nothing is valid */ aheinecke@163: return 0; aheinecke@148: } aheinecke@148: aheinecke@163: for (i = 0; to_validate[i]; i++) aheinecke@163: { aheinecke@163: bool found = false; aheinecke@163: for (j = 0; all_certs[j]; j++) aheinecke@163: { aheinecke@291: if (strncmp (to_validate[i], all_certs[j], MAX_LINE_LENGTH) == aheinecke@163: 0) aheinecke@163: { aheinecke@163: found = true; aheinecke@163: break; aheinecke@148: } aheinecke@148: } aheinecke@163: if (!found) aheinecke@163: { aheinecke@291: DEBUGPRINTF ("Failed to find certificate; \n%s\n", to_validate[i]); aheinecke@163: return ERR_INVALID_INSTRUCTIONS; aheinecke@148: } aheinecke@148: } aheinecke@65: aheinecke@163: return 0; aheinecke@64: } aheinecke@148: andre@1315: #ifdef IS_TAG_BUILD andre@1060: bool g_debug = false; andre@1072: #else andre@1072: bool g_debug = true; andre@1072: #endif aheinecke@68: aheinecke@163: int aheinecke@289: main (int argc, char **argv) aheinecke@163: { aheinecke@289: char **to_install = NULL, andre@905: **to_remove = NULL, andre@905: **all_valid_certs = NULL; aheinecke@289: int ret = -1; aheinecke@289: aheinecke@289: char *certificate_list = NULL, andre@905: *certificate_file_name = NULL, andre@905: *choices_file_name = NULL; aheinecke@163: size_t list_len = 0; aheinecke@289: list_status_t list_status; aheinecke@289: bool do_uninstall = false; aheinecke@60: andre@382: /* Some very static argument parsing. list= and choices= is only aheinecke@289: added to make it more transparent how this programm is called if aheinecke@289: a user looks at the detailed uac dialog. */ andre@1060: if ((argc != 3 && argc != 4) || strncmp(argv[1], "list=", 5) != 0 || andre@905: strncmp(argv[2], "choices=", 8) != 0) aheinecke@289: { aheinecke@289: ERRORPRINTF ("Invalid arguments.\n" aheinecke@289: "Expected arguments: list= \n" andre@1060: " choices=|uninstall\n" andre@1060: "Optional: --debug\n"); aheinecke@289: return ERR_INVALID_PARAMS; aheinecke@289: } aheinecke@289: aheinecke@289: certificate_file_name = strchr(argv[1], '=') + 1; andre@382: choices_file_name = strchr(argv[2], '=') + 1; aheinecke@289: andre@1060: if (argc == 4 && strncmp(argv[3], "--debug", 7) == 0) andre@1060: { andre@1060: g_debug = true; andre@1060: } andre@1060: andre@382: if (!certificate_file_name || !choices_file_name) aheinecke@289: { aheinecke@289: ERRORPRINTF ("Invalid arguments.\n" aheinecke@289: "Expected arguments: list= \n" andre@382: " choices=|uninstall\n"); aheinecke@289: return ERR_INVALID_PARAMS; aheinecke@289: } aheinecke@289: andre@382: if (strncmp(choices_file_name, "uninstall", 9) == 0) aheinecke@289: { aheinecke@289: do_uninstall = true; andre@382: choices_file_name = NULL; aheinecke@289: } aheinecke@289: aheinecke@289: list_status = read_and_verify_list (certificate_file_name, &certificate_list, aheinecke@289: &list_len); aheinecke@289: aheinecke@289: if (list_status != Valid) aheinecke@289: { aheinecke@289: if (list_status == InvalidSignature) aheinecke@289: { andre@382: ERRORPRINTF ("Failed to verify signature.\n"); aheinecke@289: return ERR_INVALID_SIGNATURE; aheinecke@289: } aheinecke@289: andre@382: ERRORPRINTF ("Failed to read certificate list.\n"); aheinecke@289: return ERR_INVALID_INPUT_NO_LIST; aheinecke@289: } aheinecke@289: aheinecke@289: all_valid_certs = get_certs_from_list (certificate_list, list_len); andre@944: free (certificate_list); aheinecke@289: aheinecke@289: if (!all_valid_certs) aheinecke@289: { aheinecke@289: /* Impossible */ aheinecke@289: return -1; aheinecke@289: } aheinecke@289: aheinecke@289: aheinecke@289: /* For uninstall we are done now */ aheinecke@289: if (do_uninstall) aheinecke@289: { aheinecke@290: #ifdef WIN32 andre@302: ret = write_stores_win (NULL, all_valid_certs); andre@302: if (ret != 0) andre@302: { andre@302: ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret); andre@302: } aheinecke@299: #endif andre@302: ret = write_stores_nss (NULL, all_valid_certs); andre@944: strv_free (all_valid_certs); andre@302: return ret; aheinecke@289: } aheinecke@289: andre@382: ret = read_choices_file (choices_file_name, &to_install, andre@905: &to_remove); aheinecke@148: aheinecke@163: if (ret) aheinecke@163: { andre@382: ERRORPRINTF ("Failed to read choices file\n"); aheinecke@163: return ret; aheinecke@60: } aheinecke@60: aheinecke@163: if (!strv_length (to_install) && !strv_length (to_remove) ) aheinecke@163: { andre@382: ERRORPRINTF ("Failed to read choices file\n"); aheinecke@163: return ERR_NO_INSTRUCTIONS; aheinecke@64: } aheinecke@64: andre@382: /* Check that the choices are ok to execute */ aheinecke@163: if (to_install) aheinecke@163: { andre@382: ret = validate_choices (all_valid_certs, to_install); aheinecke@163: if (ret) aheinecke@163: { andre@382: ERRORPRINTF ("Failed to validate choices\n"); aheinecke@163: return ret; aheinecke@148: } aheinecke@64: } aheinecke@68: aheinecke@163: if (to_remove) aheinecke@163: { andre@382: ret = validate_choices (all_valid_certs, to_remove); aheinecke@289: if (ret) aheinecke@163: { andre@382: ERRORPRINTF ("Failed to validate removal choices\n"); aheinecke@289: return ret; aheinecke@68: } aheinecke@68: } aheinecke@68: andre@944: strv_free (all_valid_certs); andre@944: aheinecke@68: #ifdef WIN32 andre@302: ret = write_stores_win (to_install, to_remove); andre@302: if (ret != 0) andre@302: { andre@302: ERRORPRINTF ("Failed to write windows stores retval: %i\n", ret); andre@302: } aheinecke@68: #endif andre@302: ret = write_stores_nss (to_install, to_remove); andre@302: if (ret != 0) andre@302: { andre@302: ERRORPRINTF ("Failed to write nss stores"); andre@302: } aheinecke@68: aheinecke@163: /* Make valgrind happy */ aheinecke@163: strv_free (to_install); aheinecke@163: strv_free (to_remove); aheinecke@59: aheinecke@163: return 0; aheinecke@25: }