333 lines
9.7 KiB
C
333 lines
9.7 KiB
C
/******************************************************************************/
|
|
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de> */
|
|
/* This work has been released under the CC0 1.0 Universal license! */
|
|
/******************************************************************************/
|
|
|
|
#ifdef _WIN32
|
|
# define _CRT_SECURE_NO_WARNINGS 1
|
|
#else
|
|
# define _GNU_SOURCE 1
|
|
#endif
|
|
|
|
/* Internal */
|
|
#include <slunkcrypt.h>
|
|
#include "utils.h"
|
|
#include "crypt.h"
|
|
#include "pwgen.h"
|
|
#include "selftest.h"
|
|
|
|
/* CRT */
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <inttypes.h>
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
|
|
// ==========================================================================
|
|
// Constants
|
|
// ==========================================================================
|
|
|
|
#define MODE_HELP 0
|
|
#define MODE_VERS 1
|
|
#define MODE_ENCR 2
|
|
#define MODE_DECR 3
|
|
#define MODE_PASS 4
|
|
#define MODE_TEST 5
|
|
|
|
static const size_t RCMD_PWDLEN_LENGTH = 12U;
|
|
static const size_t DFLT_PWDLEN_LENGTH = 24U;
|
|
|
|
static const CHR* const ENV_PASSWORD = T("SLUNK_PASSPHRASE");
|
|
static const CHR* const ENV_KEEPFILE = T("SLUNK_KEEP_INCOMPLETE");
|
|
|
|
static const CHR* const PREFIX_PASS = T("pass:");
|
|
static const CHR* const PREFIX_FILE = T("file:");
|
|
|
|
// ==========================================================================
|
|
// Auxiliary functions
|
|
// ==========================================================================
|
|
|
|
#define PW_FROM_ENV (!(argc > 4))
|
|
|
|
static int parse_slunk_mode(const CHR* const command)
|
|
{
|
|
if ((!STRICMP(command, T("-h"))) || (!STRICMP(command, T("/?"))) || (!STRICMP(command, T("--help"))))
|
|
{
|
|
return MODE_HELP;
|
|
}
|
|
else if ((!STRICMP(command, T("-v"))) || (!STRICMP(command, T("--version"))))
|
|
{
|
|
return MODE_VERS;
|
|
}
|
|
else if ((!STRICMP(command, T("-e"))) || (!STRICMP(command, T("--encrypt"))))
|
|
{
|
|
return MODE_ENCR;
|
|
}
|
|
else if ((!STRICMP(command, T("-d"))) || (!STRICMP(command, T("--decrypt"))))
|
|
{
|
|
return MODE_DECR;
|
|
}
|
|
else if ((!STRICMP(command, T("-p"))) || (!STRICMP(command, T("--make-pw"))))
|
|
{
|
|
return MODE_PASS;
|
|
}
|
|
else if ((!STRICMP(command, T("-t"))) || (!STRICMP(command, T("--self-test"))))
|
|
{
|
|
return MODE_TEST;
|
|
}
|
|
else
|
|
{
|
|
return -1; /*invalid command*/
|
|
}
|
|
}
|
|
|
|
static void print_manpage(const CHR *const program)
|
|
{
|
|
FPUTS(T("====================================================================\n"), stderr);
|
|
FPUTS(T("This software has been released under the CC0 1.0 Universal license:\n"), stderr);
|
|
FPUTS(T("https://creativecommons.org/publicdomain/zero/1.0/legalcode\n"), stderr);
|
|
FPUTS(T("====================================================================\n\n"), stderr);
|
|
FPUTS(T("Usage:\n"), stderr);
|
|
FPRINTF(stderr, T(" %") T(PRISTR) T(" --encrypt [pass:<pass>|file:<file>] <input.txt> <output.enc>\n"), program);
|
|
FPRINTF(stderr, T(" %") T(PRISTR) T(" --decrypt [pass:<pass>|file:<file>] <input.enc> <output.txt>\n"), program);
|
|
FPRINTF(stderr, T(" %") T(PRISTR) T(" --make-pw [<length>]\n\n"), program);
|
|
FPRINTF(stderr, T("Optionally, reads passphrase from the %") T(PRISTR) T(" environment variable.\n\n"), ENV_PASSWORD);
|
|
}
|
|
|
|
static char *copy_passphrase(const CHR *const passphrase)
|
|
{
|
|
if ((!passphrase) || (!passphrase[0U]))
|
|
{
|
|
FPUTS(T("Error: The passphrase input string must not be empty!\n\n"), stderr);
|
|
return NULL;
|
|
}
|
|
|
|
char *const buffer = CHR_to_utf8(passphrase);
|
|
if (!buffer)
|
|
{
|
|
FPUTS(T("Error: Failed to allocate the string buffer!\n\n"), stderr);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static int keep_incomplete_files(void)
|
|
{
|
|
const CHR *const keep_files = GETENV(ENV_KEEPFILE);
|
|
if (keep_files)
|
|
{
|
|
return BOOLIFY(STRTOUL(keep_files));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void check_excess_arguments(const int argc, int maximum)
|
|
{
|
|
if (argc > maximum)
|
|
{
|
|
FPUTS(T("Warning: Excess command-line argument(s) will be ignored!\n\n"), stderr);
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
|
|
static void sigint_handler(const int sig)
|
|
{
|
|
if (sig == SIGINT)
|
|
{
|
|
g_slunkcrypt_abort_flag = 1;
|
|
}
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Main function
|
|
// ==========================================================================
|
|
|
|
int MAIN(const int argc, CHR *const argv[])
|
|
{
|
|
int result = EXIT_FAILURE;
|
|
const CHR *input_file = NULL, *output_file = NULL;
|
|
char *passphrase_buffer = NULL;
|
|
|
|
init_terminal();
|
|
setup_signal_handler(SIGINT, sigint_handler);
|
|
|
|
FPRINTF(stderr, T("SlunkCrypt Utility (%") T(PRIstr) T("-%") T(PRIstr) T("), by LoRd_MuldeR <MuldeR2@GMX.de>\n"), OS_TYPE, CPU_ARCH);
|
|
FPRINTF(stderr, T("Using libSlunkCrypt v%u.%u.%u [%") T(PRIstr) T("]\n\n"), SLUNKCRYPT_VERSION_MAJOR, SLUNKCRYPT_VERSION_MINOR, SLUNKCRYPT_VERSION_PATCH, SLUNKCRYPT_BUILD);
|
|
|
|
fflush(stderr);
|
|
|
|
/* ----------------------------------------------------- */
|
|
/* Parse arguments */
|
|
/* ----------------------------------------------------- */
|
|
|
|
if ((argc < 1) || (!argv[0]))
|
|
{
|
|
FPUTS(T("Error: Argument array is empty. The program was called incorrectly!\n\n"), stderr);
|
|
goto clean_up;
|
|
}
|
|
|
|
if (argc < 2)
|
|
{
|
|
FPRINTF(stderr, T("Error: Nothing to do. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
|
|
goto clean_up;
|
|
}
|
|
|
|
const int slunk_mode = parse_slunk_mode(argv[1U]);
|
|
switch (slunk_mode)
|
|
{
|
|
case MODE_HELP:
|
|
print_manpage(get_file_name(argv[0U]));
|
|
case MODE_VERS:
|
|
result = EXIT_SUCCESS;
|
|
goto clean_up;
|
|
case MODE_ENCR:
|
|
case MODE_DECR:
|
|
break; /*fallthrough*/
|
|
case MODE_PASS:
|
|
check_excess_arguments(argc, 3);
|
|
result = generate_passphrase((argc > 2) ? STRTOUL(argv[2U]) : DFLT_PWDLEN_LENGTH);
|
|
goto clean_up;
|
|
case MODE_TEST:
|
|
check_excess_arguments(argc, 2);
|
|
result = run_selftest_routine();
|
|
goto clean_up;
|
|
default:
|
|
FPRINTF(stderr, T("Error: The specified command \"%") T(PRISTR) T("\" is unknown!\n\n"), argv[1U]);
|
|
goto clean_up;
|
|
}
|
|
|
|
if (argc < 4)
|
|
{
|
|
FPRINTF(stderr, T("Error: Required argument is missing. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
|
|
goto clean_up;
|
|
}
|
|
|
|
check_excess_arguments(argc, 5);
|
|
|
|
const CHR *const passphrase = PW_FROM_ENV ? GETENV(ENV_PASSWORD) : argv[2U];
|
|
if ((!passphrase) || (!passphrase[0U]))
|
|
{
|
|
FPUTS(T("Error: The passphrase must be specified, directly or indirectly!\n\n"), stderr);
|
|
goto clean_up;
|
|
}
|
|
|
|
if ((!PW_FROM_ENV) && STRICMP(passphrase, T("-")))
|
|
{
|
|
if ((!STARTS_WITH(passphrase, PREFIX_PASS)) && (!STARTS_WITH(passphrase, PREFIX_FILE)))
|
|
{
|
|
FPRINTF(stderr, T("Error: The passphrase must start with a '%") T(PRISTR) T("' or '%") T(PRISTR) T("' prefix!\n\n"), PREFIX_PASS, PREFIX_FILE);
|
|
goto clean_up;
|
|
}
|
|
}
|
|
|
|
input_file = argv[PW_FROM_ENV ? 2U : 3U];
|
|
if (!input_file[0U])
|
|
{
|
|
FPUTS(T("Error: The specified input file name must not be empty!\n\n"), stderr);
|
|
goto clean_up;
|
|
}
|
|
|
|
output_file = argv[PW_FROM_ENV ? 3U : 4U];
|
|
if (!output_file[0U])
|
|
{
|
|
FPUTS(T("Error: The specified output file name must not be empty!\n\n"), stderr);
|
|
goto clean_up;
|
|
}
|
|
|
|
if (same_file(input_file, output_file) > 0)
|
|
{
|
|
FPUTS(T("Error: The input and output files must not be the same!\n\n"), stderr);
|
|
goto clean_up;
|
|
}
|
|
|
|
/* ----------------------------------------------------- */
|
|
/* Initialize passphrase */
|
|
/* ----------------------------------------------------- */
|
|
|
|
if (!(passphrase_buffer = PW_FROM_ENV ? copy_passphrase(passphrase) :
|
|
(STARTS_WITH(passphrase, PREFIX_PASS) ? copy_passphrase(passphrase + STRLEN(PREFIX_PASS)) :
|
|
(STARTS_WITH(passphrase, PREFIX_FILE) ? read_passphrase(passphrase + STRLEN(PREFIX_FILE)) : read_passphrase(T("-"))))))
|
|
{
|
|
goto clean_up;
|
|
}
|
|
|
|
slunkcrypt_bzero((CHR*)passphrase, STRLEN(passphrase) * sizeof(CHR));
|
|
|
|
const size_t passphrase_len = strlen(passphrase_buffer);
|
|
if (passphrase_len < SLUNKCRYPT_PWDLEN_MIN)
|
|
{
|
|
FPRINTF(stderr, T("Error: Passphrase must be at least %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MIN);
|
|
goto clean_up;
|
|
}
|
|
else if (passphrase_len > SLUNKCRYPT_PWDLEN_MAX)
|
|
{
|
|
FPRINTF(stderr, T("Error: Passphrase must be at most %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MAX);
|
|
goto clean_up;
|
|
}
|
|
|
|
if (slunk_mode == MODE_ENCR)
|
|
{
|
|
if (passphrase_len < RCMD_PWDLEN_LENGTH)
|
|
{
|
|
FPRINTF(stderr, T("Warning: Using a *short* passphrase; a length of %u characters or more is recommended!\n\n"), (unsigned)RCMD_PWDLEN_LENGTH);
|
|
}
|
|
else if (weak_passphrase(passphrase_buffer))
|
|
{
|
|
FPUTS(T("Warning: Using a *weak* passphrase; a mix of upper-case letters, lower-case letters, digits and other characters is recommended!\n\n"), stderr);
|
|
}
|
|
}
|
|
|
|
fflush(stderr);
|
|
|
|
/* ----------------------------------------------------- */
|
|
/* Encrypt or decrypt */
|
|
/* ----------------------------------------------------- */
|
|
|
|
const uint64_t clk_start = clock_read();
|
|
const int keep_incomplete = keep_incomplete_files();
|
|
|
|
switch (slunk_mode)
|
|
{
|
|
case MODE_ENCR:
|
|
result = encrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
|
|
break;
|
|
case MODE_DECR:
|
|
result = decrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
|
|
break;
|
|
default:
|
|
FPUTS(T("Unexpected mode encountered!\n\n"), stderr);
|
|
}
|
|
|
|
if (!g_slunkcrypt_abort_flag)
|
|
{
|
|
FPRINTF(stderr, T("--------\n\nOperation completed after %.1f seconds.\n\n"), (clock_read() - clk_start) / ((double)clock_freq()));
|
|
}
|
|
|
|
/* ----------------------------------------------------- */
|
|
/* Final clean-up */
|
|
/* ----------------------------------------------------- */
|
|
|
|
clean_up:
|
|
|
|
fflush(stderr);
|
|
|
|
if (passphrase_buffer)
|
|
{
|
|
slunkcrypt_bzero(passphrase_buffer, strlen(passphrase_buffer));
|
|
free(passphrase_buffer);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
|
|
void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
|
|
int main()
|
|
{
|
|
wchar_t** enpv, ** argv;
|
|
int argc, si = 0;
|
|
__wgetmainargs(&argc, &argv, &enpv, 1, &si);
|
|
return wmain(argc, argv);
|
|
}
|
|
#endif
|