aheinecke@137: /**
aheinecke@137:  * @file main.c
aheinecke@137:  * @brief Main entry point for the cinst process.
aheinecke@25:  *
aheinecke@25:  *  The cinst 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:  *
aheinecke@289:  *  The first parameter to this process should be list=<file_name>
aheinecke@289:  *  of the certificate list to work on. The second parameter should
andre@382:  *  be choices=<choices_file_name>|uninstall
aheinecke@59:  *
andre@382:  *  choices_file_name should be the absolute path to an
andre@382:  *  choices file formatted as:
aheinecke@289:  *
aheinecke@59:  *  I:<certificate>
aheinecke@59:  *  R:<certificate>
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:  *
aheinecke@25:  **/
aheinecke@25: #include <stdio.h>
aheinecke@64: #include <stdlib.h>
aheinecke@59: #include <string.h>
aheinecke@59: #include <assert.h>
aheinecke@148: #include <stdbool.h>
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"
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,
aheinecke@289:                         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:     {
aheinecke@289:       printf ("Error invalid parameters.\n");
aheinecke@163:       return -1;
aheinecke@90:     }
aheinecke@90: 
aheinecke@289:   f = fopen (file_name, "rb");
aheinecke@289:   if (f == NULL)
aheinecke@289:     return ERR_NO_INSTRUCTIONS;
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);
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);
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:         {
aheinecke@163:           printf ("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:         {
aheinecke@163:           printf ("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: 
aheinecke@68: 
aheinecke@163: int
aheinecke@289: main (int argc, char **argv)
aheinecke@163: {
aheinecke@289:   /* TODO handle wchar arguments on Windows or do conversion dance */
aheinecke@289:   char **to_install = NULL,
aheinecke@289:        **to_remove = NULL,
aheinecke@289:        **all_valid_certs = NULL;
aheinecke@289:   int ret = -1;
aheinecke@289: 
aheinecke@289:   char *certificate_list = NULL,
aheinecke@289:        *certificate_file_name = NULL,
andre@382:        *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. */
aheinecke@289:   if (argc != 3 || strncmp(argv[1], "list=", 5) != 0 ||
andre@382:                    strncmp(argv[2], "choices=", 8) != 0)
aheinecke@289:     {
aheinecke@289:       ERRORPRINTF ("Invalid arguments.\n"
aheinecke@289:                    "Expected arguments: list=<certificate_list> \n"
andre@382:                    "                    choices=<choices_file>|uninstall\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@382:   if (!certificate_file_name || !choices_file_name)
aheinecke@289:     {
aheinecke@289:       ERRORPRINTF ("Invalid arguments.\n"
aheinecke@289:                    "Expected arguments: list=<certificate_list> \n"
andre@382:                    "                    choices=<choices_file>|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);
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@302:       return ret;
aheinecke@289:     }
aheinecke@289: 
andre@382:   ret = read_choices_file (choices_file_name, &to_install,
aheinecke@289:                                 &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: 
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@382:       DEBUGPRINTF ("Hello World");
andre@302:     }
aheinecke@68: 
aheinecke@163:   /* Make valgrind happy */
aheinecke@163:   strv_free (to_install);
aheinecke@163:   strv_free (to_remove);
aheinecke@163:   free (certificate_list);
aheinecke@59: 
aheinecke@163:   return 0;
aheinecke@25: }