504 lines
13 KiB
C
504 lines
13 KiB
C
/* recfix.c - Fix and analyze a recfile. */
|
|
|
|
/* Copyright (C) 2010-2022 Jose E. Marchesi */
|
|
|
|
/* 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/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <getopt.h>
|
|
#include <stdlib.h>
|
|
#include <xalloc.h>
|
|
#include <gettext.h>
|
|
#define _(str) gettext (str)
|
|
|
|
#include <rec.h>
|
|
#include <recutl.h>
|
|
|
|
/*
|
|
* Data types.
|
|
*/
|
|
|
|
/* recfix supports several operations, enumerated in the following
|
|
type. */
|
|
|
|
enum recfix_op
|
|
{
|
|
RECFIX_OP_INVALID,
|
|
RECFIX_OP_CHECK,
|
|
#if defined REC_CRYPT_SUPPORT
|
|
RECFIX_OP_ENCRYPT,
|
|
RECFIX_OP_DECRYPT,
|
|
#endif
|
|
RECFIX_OP_SORT,
|
|
RECFIX_OP_AUTO
|
|
};
|
|
|
|
/*
|
|
* Global variables.
|
|
*/
|
|
|
|
bool recfix_external = true;
|
|
char *recfix_file = NULL;
|
|
int recfix_op = RECFIX_OP_INVALID;
|
|
char *recfix_password = NULL;
|
|
bool recfix_force = false;
|
|
|
|
/*
|
|
* Command line options management.
|
|
*/
|
|
|
|
enum
|
|
{
|
|
COMMON_ARGS,
|
|
NO_EXTERNAL_ARG,
|
|
FORCE_ARG,
|
|
OP_SORT_ARG,
|
|
#if defined REC_CRYPT_SUPPORT
|
|
PASSWORD_ARG,
|
|
OP_ENCRYPT_ARG,
|
|
OP_DECRYPT_ARG,
|
|
#endif
|
|
OP_CHECK_ARG,
|
|
OP_AUTO_ARG
|
|
};
|
|
|
|
static const struct option GNU_longOptions[] =
|
|
{
|
|
COMMON_LONG_ARGS,
|
|
{"no-external", no_argument, NULL, NO_EXTERNAL_ARG},
|
|
{"force", no_argument, NULL, FORCE_ARG},
|
|
{"check", no_argument, NULL, OP_CHECK_ARG},
|
|
{"sort", no_argument, NULL, OP_SORT_ARG},
|
|
#if defined REC_CRYPT_SUPPORT
|
|
{"password", required_argument, NULL, PASSWORD_ARG},
|
|
{"encrypt", no_argument, NULL, OP_ENCRYPT_ARG},
|
|
{"decrypt", no_argument, NULL, OP_DECRYPT_ARG},
|
|
#endif
|
|
{"auto", no_argument, NULL, OP_AUTO_ARG},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
void
|
|
recutl_print_help (void)
|
|
{
|
|
/* TRANSLATORS: --help output, recfix synopsis.
|
|
no-wrap */
|
|
printf (_("\
|
|
Usage: recfix [OPTION]... [OPERATION] [OP_OPTION]... [FILE]\n"));
|
|
|
|
/* TRANSLATORS: --help output, recfix short description.
|
|
no-wrap */
|
|
fputs (_("\
|
|
Check and fix rec files.\n"),
|
|
stdout);
|
|
|
|
puts ("");
|
|
/* TRANSLATORS: --help output, recfix global arguments.
|
|
no-wrap */
|
|
fputs (_("\
|
|
--no-external don't use external descriptors.\n\
|
|
--force force the requested operation.\n"),
|
|
stdout);
|
|
|
|
recutl_print_help_common ();
|
|
|
|
puts("");
|
|
/* TRANSLATORS: --help output, recfix operations.
|
|
no-wrap */
|
|
fputs (_("\
|
|
Operations:\n\
|
|
--check check integrity of the specified file. Default.\n\
|
|
--sort sort the records in the specified file.\n\
|
|
--auto insert auto-generated fields in records missing them.\n"),
|
|
stdout);
|
|
|
|
#if defined REC_CRYPT_SUPPORT
|
|
/* TRANSLATORS: --help output, recfix operations related with encryption.
|
|
no-wrap */
|
|
fputs (_("\
|
|
--encrypt encrypt confidential fields in the specified file.\n\
|
|
--decrypt decrypt confidential fields in the specified file.\n"),
|
|
stdout);
|
|
|
|
puts("");
|
|
/* TRANSLATORS: --help output, recfix encryption and decryption
|
|
options.
|
|
no-wrap */
|
|
fputs (_("\
|
|
De/Encryption options:\n\
|
|
-s, --password=PASSWORD encrypt/decrypt with this password.\n"),
|
|
stdout);
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
|
|
puts("");
|
|
/* TRANSLATORS: --help output, notes on recfix.
|
|
no-wrap */
|
|
fputs (_("\
|
|
If no FILE is specified then the command acts like a filter, getting\n\
|
|
the data from standard input and writing the result to standard output.\n"), stdout);
|
|
|
|
puts("");
|
|
recutl_print_help_footer ();
|
|
}
|
|
|
|
static void
|
|
recfix_parse_args (int argc,
|
|
char **argv)
|
|
{
|
|
char c;
|
|
int ret;
|
|
|
|
while ((ret = getopt_long (argc,
|
|
argv,
|
|
ENCRYPTION_SHORT_ARGS,
|
|
GNU_longOptions,
|
|
NULL)) != -1)
|
|
{
|
|
c = ret;
|
|
switch (c)
|
|
{
|
|
COMMON_ARGS_CASES
|
|
case NO_EXTERNAL_ARG:
|
|
recfix_external = false;
|
|
break;
|
|
case FORCE_ARG:
|
|
recfix_force = true;
|
|
break;
|
|
#if defined REC_CRYPT_SUPPORT
|
|
case 's':
|
|
case PASSWORD_ARG:
|
|
if (recfix_op == RECFIX_OP_INVALID)
|
|
recutl_fatal (_("--password|-s must be used as an operation argument.\n"));
|
|
|
|
if ((recfix_op != RECFIX_OP_ENCRYPT)
|
|
&& (recfix_op != RECFIX_OP_DECRYPT))
|
|
recutl_fatal (_("the specified operation does not require a password.\n"));
|
|
|
|
if (recfix_password != NULL)
|
|
recutl_fatal (_("please specify just one password.\n"));
|
|
|
|
recfix_password = xstrdup (optarg);
|
|
break;
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
case OP_CHECK_ARG:
|
|
if (recfix_op != RECFIX_OP_INVALID)
|
|
recutl_fatal (_("please specify just one operation.\n"));
|
|
|
|
recfix_op = RECFIX_OP_CHECK;
|
|
break;
|
|
case OP_SORT_ARG:
|
|
if (recfix_op != RECFIX_OP_INVALID)
|
|
recutl_fatal (_("please specify just one operation.\n"));
|
|
|
|
recfix_op = RECFIX_OP_SORT;
|
|
break;
|
|
case OP_AUTO_ARG:
|
|
if (recfix_op != RECFIX_OP_INVALID)
|
|
recutl_fatal (_("please specify just one operation.\n"));
|
|
|
|
recfix_op = RECFIX_OP_AUTO;
|
|
break;
|
|
#if defined REC_CRYPT_SUPPORT
|
|
case OP_ENCRYPT_ARG:
|
|
if (recfix_op != RECFIX_OP_INVALID)
|
|
recutl_fatal (_("please specify just one operation.\n"));
|
|
|
|
recfix_op = RECFIX_OP_ENCRYPT;
|
|
break;
|
|
case OP_DECRYPT_ARG:
|
|
if (recfix_op != RECFIX_OP_INVALID)
|
|
recutl_fatal (_("please specify just one operation.\n"));
|
|
|
|
recfix_op = RECFIX_OP_DECRYPT;
|
|
break;
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
default:
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* The default operation is check, in case the user did not specify
|
|
any in the command line. */
|
|
if (recfix_op == RECFIX_OP_INVALID)
|
|
recfix_op = RECFIX_OP_CHECK;
|
|
|
|
#if defined REC_CRYPT_SUPPORT
|
|
/* The encrypt and decrypt operations require the user to specify a
|
|
password. If no password was specified with -s and the program
|
|
is running in a terminal, prompt the user to provide the
|
|
password. */
|
|
|
|
if (((recfix_op == RECFIX_OP_ENCRYPT)
|
|
|| (recfix_op == RECFIX_OP_DECRYPT))
|
|
&& (recfix_password == NULL))
|
|
{
|
|
if (recutl_interactive ())
|
|
{
|
|
if (recfix_op == RECFIX_OP_ENCRYPT)
|
|
recfix_password = recutl_getpass (true);
|
|
else
|
|
recfix_password = recutl_getpass (false);
|
|
}
|
|
|
|
if (!recfix_password || (strlen (recfix_password) == 0))
|
|
recutl_fatal ("please specify a password.\n");
|
|
}
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
|
|
/* Read the name of the file to work on. */
|
|
if (optind < argc)
|
|
{
|
|
if ((argc - optind) != 1)
|
|
{
|
|
recutl_print_help ();
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
recfix_file = argv[optind++];
|
|
}
|
|
}
|
|
|
|
static bool
|
|
recfix_check_database (rec_db_t db)
|
|
{
|
|
bool ret;
|
|
char *errors;
|
|
size_t errors_size;
|
|
rec_buf_t buf;
|
|
|
|
buf = rec_buf_new (&errors, &errors_size);
|
|
ret = (rec_int_check_db (db,
|
|
true, /* Check descriptors. */
|
|
recfix_external, /* Use external descriptors. */
|
|
buf) == 0);
|
|
rec_buf_close (buf);
|
|
fprintf (stderr, "%s", errors);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
recfix_do_check ()
|
|
{
|
|
rec_db_t db;
|
|
|
|
/* Read the database from the specified file and check its
|
|
integrity. */
|
|
|
|
db = recutl_read_db_from_file (recfix_file);
|
|
if (!db)
|
|
{
|
|
fprintf (stderr, _("recfix: error: cannot read file %s\n"), recfix_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!recfix_check_database (db))
|
|
return EXIT_FAILURE;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
recfix_do_sort ()
|
|
{
|
|
rec_db_t db = NULL;
|
|
size_t n_rset = 0;
|
|
rec_rset_t rset = NULL;
|
|
|
|
/* Read the database from the specified file. */
|
|
|
|
db = recutl_read_db_from_file (recfix_file);
|
|
if (!db)
|
|
{
|
|
fprintf (stderr, _("recfix: error: cannot read file %s\n"), recfix_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Sort all the record sets contained in the database. */
|
|
|
|
for (n_rset = 0; n_rset < rec_db_size (db); n_rset++)
|
|
{
|
|
rset = rec_db_get_rset (db, n_rset);
|
|
if (!rec_rset_sort (rset, NULL))
|
|
recutl_out_of_memory ();
|
|
}
|
|
|
|
if (!recfix_check_database (db))
|
|
return EXIT_FAILURE;
|
|
|
|
if (!recutl_file_is_writable (recfix_file))
|
|
{
|
|
recutl_error (_("file %s is not writable.\n"), recfix_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
recutl_write_db_to_file (db, recfix_file);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
#if defined REC_CRYPT_SUPPORT
|
|
|
|
static int
|
|
recfix_do_crypt ()
|
|
{
|
|
rec_db_t db;
|
|
size_t n_rset;
|
|
|
|
/* Read the database from the specified file. */
|
|
|
|
db = recutl_read_db_from_file (recfix_file);
|
|
if (!db)
|
|
{
|
|
fprintf (stderr, _("recfix: error: cannot read file %s\n"), recfix_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Encrypt/decrypt any unencrypted/encrypted field marked as
|
|
"confidential" using the given password. */
|
|
|
|
for (n_rset = 0; n_rset < rec_db_size (db); n_rset++)
|
|
{
|
|
rec_mset_iterator_t iter;
|
|
rec_fex_t confidential_fields;
|
|
rec_record_t record;
|
|
rec_rset_t rset =
|
|
rec_db_get_rset (db, n_rset);
|
|
|
|
/* Skip record sets not having any confidential fields. */
|
|
|
|
confidential_fields = rec_rset_confidential (rset);
|
|
if (confidential_fields == NULL)
|
|
continue;
|
|
|
|
/* Process every record of the record set. */
|
|
|
|
iter = rec_mset_iterator (rec_rset_mset (rset));
|
|
while (rec_mset_iterator_next (&iter, MSET_RECORD, (const void **) &record, NULL))
|
|
{
|
|
if (recfix_op == RECFIX_OP_ENCRYPT)
|
|
{
|
|
/* Encrypt any unencrypted confidential field in this
|
|
record. */
|
|
|
|
if (!rec_encrypt_record (rset, record, recfix_password)
|
|
&& !recfix_force)
|
|
{
|
|
recutl_error (_("the database contains already encrypted fields\n"));
|
|
recutl_fatal (_("please use --force or --decrypt\n"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Decrypt any encrypted confidential field in this
|
|
record. */
|
|
|
|
rec_decrypt_record (rset, record, recfix_password);
|
|
}
|
|
}
|
|
|
|
rec_mset_iterator_free (&iter);
|
|
}
|
|
|
|
/* Write the modified database back to the file. */
|
|
|
|
recutl_write_db_to_file (db, recfix_file);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
|
|
static int
|
|
recfix_do_auto ()
|
|
{
|
|
rec_db_t db = NULL;
|
|
size_t n_rset = 0;
|
|
|
|
/* Read the database from the especified file. */
|
|
|
|
db = recutl_read_db_from_file (recfix_file);
|
|
if (!db)
|
|
{
|
|
fprintf (stderr, _("recfix: error: cannot read file %s\n"), recfix_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Add auto fields to any record in the database not having it, in
|
|
record sets featuring auto fields. */
|
|
|
|
for (n_rset = 0; n_rset < rec_db_size (db); n_rset++)
|
|
{
|
|
rec_mset_iterator_t iter;
|
|
rec_record_t record;
|
|
rec_rset_t rset = rec_db_get_rset (db, n_rset);
|
|
|
|
iter = rec_mset_iterator (rec_rset_mset (rset));
|
|
while (rec_mset_iterator_next (&iter, MSET_RECORD, (const void**) &record, NULL))
|
|
{
|
|
if (!rec_rset_add_auto_fields (rset, record))
|
|
recutl_out_of_memory ();
|
|
}
|
|
|
|
rec_mset_iterator_free (&iter);
|
|
}
|
|
|
|
if (!recfix_check_database (db))
|
|
return EXIT_FAILURE;
|
|
|
|
recutl_write_db_to_file (db, recfix_file);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
int res = EXIT_SUCCESS;
|
|
|
|
recutl_init ("recfix");
|
|
|
|
/* Parse arguments. */
|
|
|
|
recfix_parse_args (argc, argv);
|
|
|
|
/* Execute the proper operation as specified in the recfix_op
|
|
variable. */
|
|
|
|
switch (recfix_op)
|
|
{
|
|
case RECFIX_OP_CHECK:
|
|
res = recfix_do_check ();
|
|
break;
|
|
case RECFIX_OP_SORT:
|
|
res = recfix_do_sort ();
|
|
break;
|
|
case RECFIX_OP_AUTO:
|
|
res = recfix_do_auto ();
|
|
break;
|
|
#if defined REC_CRYPT_SUPPORT
|
|
case RECFIX_OP_ENCRYPT:
|
|
case RECFIX_OP_DECRYPT:
|
|
res = recfix_do_crypt ();
|
|
break;
|
|
#endif /* REC_CRYPT_SUPPORT */
|
|
default:
|
|
/* This point shall not be reached. */
|
|
res = EXIT_FAILURE;
|
|
recutl_fatal (_("unknown operation in recfix: please report this as a bug.\n"));
|
|
}
|
|
|
|
return res;
|
|
}
|