/*
|
|
* gfsecret - Secret sharing tools
|
|
* Copyright (C) 2016,2017,2018,2019,2020 Damien Goutte-Gattat
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <locale.h>
|
|
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <err.h>
|
|
|
|
#include <gcrypt.h>
|
|
|
|
#include <xmem.h>
|
|
|
|
#include "util.h"
|
|
#include "secretcfg.h"
|
|
#include "scheme-module.h"
|
|
|
|
static void
|
|
usage(int status)
|
|
{
|
|
puts(_("Usage: gfsec-split [options] file uri...\n\
|
|
Split the specified file for later use with gfsec-use.\n"));
|
|
|
|
puts(_("Options:\n\
|
|
-h, --help Display this help message.\n\
|
|
-v, --version Display the version message.\n"));
|
|
|
|
puts(_("\
|
|
-n, --threshold N Specify the minimal number of shares\n\
|
|
required to re-assemble the secret.\n\
|
|
Default is 2.\n\
|
|
-s, --share URI Add a share.\n\
|
|
-i, --interactive Prompt the user for share URIs.\n"));
|
|
|
|
puts(_("\
|
|
-c, --config FILE Write configuration to the specified\n\
|
|
file. Default is the basename of the\n\
|
|
split file in gfsecret's configuration\n\
|
|
directory.\n"));
|
|
|
|
puts(_("\
|
|
-k, --keep Do not delete the original file.\n"));
|
|
|
|
puts(_("\
|
|
-o, --output FILE Specify the location where the file will\n\
|
|
be reconstructed by gfsec-use. The default\n\
|
|
is to reconstruct the file at its original\n\
|
|
location.\n\
|
|
-r, --restore-cmd CMD Specify the command to restore the secret.\n\
|
|
-d, --destroy-cmd CMD Specify the command to delete the secret.\n"));
|
|
|
|
puts(_("\
|
|
-l, --list-supports List available supports and exit.\n"));
|
|
|
|
printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
|
|
|
exit(status);
|
|
}
|
|
|
|
static void
|
|
info(void)
|
|
{
|
|
printf(_("\
|
|
gfsec-split (%s %s)\n\
|
|
Copyright (C) 2020 Damien Goutte-Gattat\n\
|
|
\n\
|
|
This program is released under the GNU General Public License.\n\
|
|
See the COPYING file or <http://www.gnu.org/licenses/gpl.html>.\n\
|
|
"), PACKAGE_NAME, VERSION);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
static char *
|
|
ask(const char *prompt)
|
|
{
|
|
char *buffer = NULL;
|
|
size_t len = 0;
|
|
ssize_t n;
|
|
|
|
printf("%s ", prompt);
|
|
|
|
if ( (n = getline(&buffer, &len, stdin)) == -1 )
|
|
err(EXIT_FAILURE, _("Cannot read from standard input"));
|
|
else if ( n > 0 && buffer[n - 1] == '\n' )
|
|
buffer[n - 1] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char *
|
|
ask_nonempty(const char *prompt)
|
|
{
|
|
char *answer;
|
|
int done = 0;
|
|
|
|
do {
|
|
answer = ask(prompt);
|
|
if ( ! (done = *answer != '\0') )
|
|
free(answer);
|
|
} while ( ! done );
|
|
|
|
return answer;
|
|
}
|
|
|
|
static int
|
|
ask_confirmation(const char *prompt)
|
|
{
|
|
int rc = 0;
|
|
char *answer;
|
|
|
|
answer = ask(prompt);
|
|
if ( (*answer == 'y' || *answer == 'Y') && *(answer + 1) == '\0' )
|
|
rc = 1;
|
|
|
|
free(answer);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
prompt_for_share(gfsec_secret_t *cfg, gfsec_supports_list_t *list, unsigned number)
|
|
{
|
|
int i, rc;
|
|
char *answer, *path;
|
|
gfsec_share_t *share;
|
|
|
|
rc = -1;
|
|
|
|
printf(_("Select a support for share #%u:\n\n"), number);
|
|
for ( i = 0; (unsigned)i < list->count; i++ )
|
|
printf(" (%d) %s\n", i+1, list->supports[i].description);
|
|
printf(_(" (x) Done\n\n"));
|
|
|
|
while ( rc == -1 ) {
|
|
answer = ask (_("Your choice?"));
|
|
|
|
if ( (*answer == 'x' || *answer == 'X') && *(answer + 1) == '\0' )
|
|
rc = 1; /* We're done selecting supports. */
|
|
else {
|
|
int selected;
|
|
|
|
selected = strtol(answer, NULL, 10);
|
|
if ( selected > 0 && (unsigned)selected < list->count + 1 ) {
|
|
path = ask_nonempty(_("Specify a pathname on this support:"));
|
|
|
|
share = gfsec_share_new();
|
|
gfsec_share_set_info(share, GFSEC_SHARE_NUMBER_AUTOASSIGN,
|
|
list->supports[selected - 1].scheme,
|
|
list->supports[selected - 1].authority,
|
|
path, NULL);
|
|
if ( (i = gfsec_secret_add_share(cfg, share)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot add share: %s"), gfsec_error_string(i));
|
|
|
|
rc = 0; /* Done for this share. */
|
|
|
|
free(path);
|
|
}
|
|
}
|
|
|
|
free(answer);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
interactive_loop(gfsec_secret_t *cfg)
|
|
{
|
|
gfsec_supports_list_t *list;
|
|
int i;
|
|
char buffer[255];
|
|
|
|
list = gfsec_scheme_module_get_supports();
|
|
i = 0;
|
|
|
|
while ( ! prompt_for_share(cfg, list, ++i) ) ;
|
|
|
|
gfsec_support_destroy_list(list);
|
|
|
|
printf(_("\nThe following shares will be created:\n"));
|
|
for ( i = 0; i < cfg->n_shares; i++ ) {
|
|
gfsec_share_get_uri(cfg->shares[i], buffer, sizeof(buffer));
|
|
printf(" %s\n", buffer);
|
|
}
|
|
putchar('\n');
|
|
|
|
if ( ! ask_confirmation(_("Proceed (y/N)?")) )
|
|
errx(EXIT_FAILURE, _("Split cancelled"));
|
|
}
|
|
|
|
static void
|
|
transform_config_pathname(char **cfg, const char *secret)
|
|
{
|
|
char *base = NULL;
|
|
|
|
if ( ! *cfg )
|
|
base = get_file_basename(secret);
|
|
else if ( ! strchr(*cfg, '/') && ! strchr(*cfg, '.') )
|
|
base = xstrdup(*cfg);
|
|
|
|
if ( base ) {
|
|
const char *env_info;
|
|
char *config_dir = NULL;
|
|
|
|
if ( (env_info = getenv("XDG_CONFIG_HOME")) && *env_info)
|
|
xasprintf(&config_dir, "%s/gfsecret", env_info);
|
|
else if ( (env_info = getenv("HOME")) && *env_info )
|
|
xasprintf(&config_dir, "%s/.config/gfsecret", env_info);
|
|
|
|
if ( config_dir ) {
|
|
if ( makedir(config_dir, 0700) == -1 )
|
|
err(EXIT_FAILURE, "Cannot create configuration directory");
|
|
|
|
xasprintf(cfg, "%s/%s.conf", config_dir, base);
|
|
free(config_dir);
|
|
}
|
|
else
|
|
xasprintf(cfg, "%s.conf", base);
|
|
|
|
free(base);
|
|
}
|
|
}
|
|
|
|
static unsigned
|
|
get_uinteger_or_die(const char *arg)
|
|
{
|
|
unsigned val;
|
|
char *endptr;
|
|
|
|
errno = 0;
|
|
val = strtoul(arg, &endptr, 10);
|
|
if ( errno != 0 || endptr == arg )
|
|
errx(EXIT_FAILURE, _("Invalid argument, unsigned integer expected: %s"), arg);
|
|
|
|
return val;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int c, list_mode, interactive;
|
|
unsigned threshold, keep_original;
|
|
char *config_path, *secret_file, *output_file;
|
|
gfsec_secret_t *cfg;
|
|
|
|
struct option options[] = {
|
|
{ "help", 0, NULL, 'h' },
|
|
{ "version", 0, NULL, 'v' },
|
|
{ "threshold", 1, NULL, 'n' },
|
|
/* Keep bogus spelling for compatibility. */
|
|
{ "treshold", 1, NULL, 'n' },
|
|
{ "share", 1, NULL, 's' },
|
|
{ "interactive", 0, NULL, 'i' },
|
|
{ "config", 1, NULL, 'c' },
|
|
{ "keep", 0, NULL, 'k' },
|
|
{ "output", 1, NULL, 'o' },
|
|
{ "restore-cmd", 1, NULL, 'r' },
|
|
{ "destroy-cmd", 1, NULL, 'd' },
|
|
{ "list-supports", 0, NULL, 'l' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
setprogname(argv[0]);
|
|
threshold = 2;
|
|
keep_original = list_mode = interactive = 0;
|
|
config_path = output_file = NULL;
|
|
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
|
|
srandom(time(NULL));
|
|
|
|
cfg = gfsec_secret_new();
|
|
|
|
while ( (c = getopt_long(argc, argv, "hvn:s:ic:ko:r:d:l",
|
|
options, NULL)) != -1 ) {
|
|
switch ( c ) {
|
|
case 'h':
|
|
usage(EXIT_SUCCESS);
|
|
break;
|
|
|
|
case '?':
|
|
usage(EXIT_FAILURE);
|
|
break;
|
|
|
|
case 'v':
|
|
info();
|
|
break;
|
|
|
|
case 'n':
|
|
threshold = get_uinteger_or_die(optarg);
|
|
break;
|
|
|
|
case 's':
|
|
if ( (c = gfsec_parse_uri(optarg, cfg, 1)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot add share: %s"), gfsec_error_string(c));
|
|
break;
|
|
|
|
case 'i':
|
|
interactive = 1;
|
|
break;
|
|
|
|
case 'c':
|
|
config_path = optarg;
|
|
break;
|
|
|
|
case 'k':
|
|
keep_original = 1;
|
|
break;
|
|
|
|
case 'o':
|
|
output_file = optarg;
|
|
break;
|
|
|
|
case 'r':
|
|
cfg->restore = xstrdup(optarg);
|
|
break;
|
|
|
|
case 'd':
|
|
cfg->destroy = xstrdup(optarg);
|
|
break;
|
|
|
|
case 'l':
|
|
list_mode = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gfsec_scheme_module_init();
|
|
|
|
if ( list_mode ) {
|
|
gfsec_supports_list_t *list;
|
|
char *schemes[] = { "file", "uuid", "label", "mtp" };
|
|
char fmt[32], name[64];
|
|
unsigned i, len, max_len;
|
|
|
|
list = gfsec_scheme_module_get_supports();
|
|
|
|
for ( i = max_len = 0; i < list->count; i++ ) {
|
|
len = snprintf(NULL, 0, "%s://%s/ ",
|
|
schemes[list->supports[i].scheme],
|
|
list->supports[i].authority);
|
|
if ( len > max_len )
|
|
max_len = len;
|
|
}
|
|
snprintf(fmt, sizeof(fmt), "%%-%ds%%s\n", max_len);
|
|
|
|
for ( i = 0; i < list->count; i++ ) {
|
|
snprintf(name, sizeof(name), "%s://%s/",
|
|
schemes[list->supports[i].scheme],
|
|
list->supports[i].authority);
|
|
printf(fmt, name, list->supports[i].description);
|
|
}
|
|
|
|
gfsec_support_destroy_list(list);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
if ( ! gcry_check_version(GCRYPT_VERSION) )
|
|
errx(EXIT_FAILURE, _("libgcrypt version mismatch"));
|
|
|
|
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
|
|
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
|
|
|
|
if ( optind >= argc )
|
|
errx(EXIT_FAILURE, _("Missing secret file"));
|
|
secret_file = argv[optind++];
|
|
|
|
transform_config_pathname(&config_path, secret_file);
|
|
if ( file_exists(config_path) != -1 ) {
|
|
warn(_("Configuration file %s already exists"), config_path);
|
|
if ( ! ask_confirmation(_("Overwrite (y/N)?")) )
|
|
errx(EXIT_FAILURE, _("Split cancelled"));
|
|
}
|
|
|
|
if ( (c = gfsec_secret_set_secret_file(cfg, secret_file, output_file)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot set secret: %s"), gfsec_error_string(c));
|
|
|
|
while ( optind < argc ) {
|
|
if ( (c = gfsec_parse_uri(argv[optind++], cfg, 1)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot add share: %s"), gfsec_error_string(c));
|
|
}
|
|
|
|
if ( interactive )
|
|
interactive_loop(cfg);
|
|
|
|
if ( (c = gfsec_secret_split(cfg, threshold)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot split secret: %s"), gfsec_error_string(c));
|
|
|
|
for ( c = 0; c < cfg->n_shares; c++ ) {
|
|
gfsec_share_t *share;
|
|
|
|
share = cfg->shares[c];
|
|
if ( gfsec_scheme_module_put_file(share->scheme, share->authority,
|
|
share->path, share->data, share->len)
|
|
!= GFSEC_SCHEME_STATUS_SUCCESS )
|
|
errx(EXIT_FAILURE, _("Cannot write share"));
|
|
}
|
|
|
|
if ( (c = gfsec_write_config(cfg, config_path)) != 0 )
|
|
errx(EXIT_FAILURE, _("Cannot write configuration file: %s"),
|
|
gfsec_error_string(c));
|
|
|
|
if ( ! keep_original ) {
|
|
if ( cfg->destroy ) {
|
|
int status;
|
|
|
|
status = system(cfg->destroy);
|
|
if ( ! WIFEXITED(status) )
|
|
errx(EXIT_FAILURE, _("Delete command terminated anormally"));
|
|
}
|
|
|
|
if ( unlink(secret_file) == -1 )
|
|
err(EXIT_FAILURE, _("Cannot delete original file"));
|
|
}
|
|
|
|
gfsec_secret_free(cfg);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|